diff --git a/tests/test_swf/responses/test_activity_tasks.py b/tests/test_swf/responses/test_activity_tasks.py index 0b72b7ca7..f23d146b9 100644 --- a/tests/test_swf/responses/test_activity_tasks.py +++ b/tests/test_swf/responses/test_activity_tasks.py @@ -1,14 +1,20 @@ from boto.swf.exceptions import SWFResponseError +from botocore.exceptions import ClientError from freezegun import freeze_time +from unittest import SkipTest +import pytest import sure # noqa -from moto import mock_swf_deprecated +from moto import mock_swf, mock_swf_deprecated +from moto import settings from moto.swf import swf_backend from ..utils import setup_workflow, SCHEDULE_ACTIVITY_TASK_DECISION +from ..utils import setup_workflow_boto3 # PollForActivityTask endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_poll_for_activity_task_when_one(): conn = setup_workflow() @@ -31,6 +37,34 @@ def test_poll_for_activity_task_when_one(): ) +@mock_swf +def test_poll_for_activity_task_when_one_boto3(): + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + resp = client.poll_for_activity_task( + domain="test-domain", + taskList={"name": "activity-task-list"}, + identity="surprise", + ) + resp["activityId"].should.equal("my-activity-001") + resp["taskToken"].should_not.be.none + + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + resp["events"][-1]["eventType"].should.equal("ActivityTaskStarted") + resp["events"][-1]["activityTaskStartedEventAttributes"].should.equal( + {"identity": "surprise", "scheduledEventId": 5} + ) + + +# Has boto3 equivalent @mock_swf_deprecated def test_poll_for_activity_task_when_none(): conn = setup_workflow() @@ -38,6 +72,7 @@ def test_poll_for_activity_task_when_none(): resp.should.equal({"startedEventId": 0}) +# Has boto3 equivalent @mock_swf_deprecated def test_poll_for_activity_task_on_non_existent_queue(): conn = setup_workflow() @@ -45,7 +80,20 @@ def test_poll_for_activity_task_on_non_existent_queue(): resp.should.equal({"startedEventId": 0}) +@pytest.mark.parametrize("task_name", ["activity-task-list", "non-existent-queue"]) +@mock_swf +def test_poll_for_activity_task_when_none_boto3(task_name): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": task_name} + ) + resp.shouldnt.have.key("taskToken") + resp.should.have.key("startedEventId").equal(0) + resp.should.have.key("previousStartedEventId").equal(0) + + # CountPendingActivityTasks endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_count_pending_activity_tasks(): conn = setup_workflow() @@ -58,6 +106,7 @@ def test_count_pending_activity_tasks(): resp.should.equal({"count": 1, "truncated": False}) +# Has boto3 equivalent @mock_swf_deprecated def test_count_pending_decision_tasks_on_non_existent_task_list(): conn = setup_workflow() @@ -65,7 +114,28 @@ def test_count_pending_decision_tasks_on_non_existent_task_list(): resp.should.equal({"count": 0, "truncated": False}) +@pytest.mark.parametrize( + "task_name,cnt", [("activity-task-list", 1), ("non-existent", 0)] +) +@mock_swf +def test_count_pending_activity_tasks_boto3(task_name, cnt): + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + + resp = client.count_pending_activity_tasks( + domain="test-domain", taskList={"name": task_name} + ) + resp.should.have.key("count").equal(cnt) + resp.should.have.key("truncated").equal(False) + + # RespondActivityTaskCompleted endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_respond_activity_task_completed(): conn = setup_workflow() @@ -91,6 +161,34 @@ def test_respond_activity_task_completed(): ) +@mock_swf +def test_respond_activity_task_completed_boto3(): + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + activity_token = client.poll_for_activity_task( + domain="test-domain", taskList={"name": "activity-task-list"} + )["taskToken"] + + client.respond_activity_task_completed( + taskToken=activity_token, result="result of the task" + ) + + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + resp["events"][-2]["eventType"].should.equal("ActivityTaskCompleted") + resp["events"][-2]["activityTaskCompletedEventAttributes"].should.equal( + {"result": "result of the task", "scheduledEventId": 5, "startedEventId": 6} + ) + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_activity_task_completed_on_closed_workflow_execution(): conn = setup_workflow() @@ -113,6 +211,33 @@ def test_respond_activity_task_completed_on_closed_workflow_execution(): ) +@mock_swf +def test_respond_activity_task_completed_on_closed_workflow_execution_boto3(): + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + activity_token = client.poll_for_activity_task( + domain="test-domain", taskList={"name": "activity-task-list"} + )["taskToken"] + + client.terminate_workflow_execution(domain="test-domain", workflowId="uid-abcd1234") + + with pytest.raises(ClientError) as ex: + client.respond_activity_task_completed(taskToken=activity_token) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown execution: WorkflowExecution=[workflowId=uid-abcd1234, runId={}]".format( + client.run_id + ) + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_activity_task_completed_with_task_already_completed(): conn = setup_workflow() @@ -131,7 +256,32 @@ def test_respond_activity_task_completed_with_task_already_completed(): ) +@mock_swf +def test_respond_activity_task_completed_with_task_already_completed_boto3(): + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + activity_token = client.poll_for_activity_task( + domain="test-domain", taskList={"name": "activity-task-list"} + )["taskToken"] + + client.respond_activity_task_completed(taskToken=activity_token) + + with pytest.raises(ClientError) as ex: + client.respond_activity_task_completed(taskToken=activity_token) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown activity, scheduledEventId = 5" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + # RespondActivityTaskFailed endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_respond_activity_task_failed(): conn = setup_workflow() @@ -162,6 +312,39 @@ def test_respond_activity_task_failed(): ) +@mock_swf +def test_respond_activity_task_failed_boto3(): + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + activity_token = client.poll_for_activity_task( + domain="test-domain", taskList={"name": "activity-task-list"} + )["taskToken"] + + client.respond_activity_task_failed( + taskToken=activity_token, reason="short reason", details="long details" + ) + + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + resp["events"][-2]["eventType"].should.equal("ActivityTaskFailed") + resp["events"][-2]["activityTaskFailedEventAttributes"].should.equal( + { + "reason": "short reason", + "details": "long details", + "scheduledEventId": 5, + "startedEventId": 6, + } + ) + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_activity_task_completed_with_wrong_token(): # NB: we just test ONE failure case for RespondActivityTaskFailed @@ -178,7 +361,31 @@ def test_respond_activity_task_completed_with_wrong_token(): ).should.throw(SWFResponseError, "Invalid token") +@mock_swf +def test_respond_activity_task_completed_with_wrong_token_boto3(): + # NB: we just test ONE failure case for RespondActivityTaskFailed + # because the safeguards are shared with RespondActivityTaskCompleted, so + # no need to retest everything end-to-end. + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + client.poll_for_activity_task( + domain="test-domain", taskList={"name": "activity-task-list"} + )["taskToken"] + + with pytest.raises(ClientError) as ex: + client.respond_activity_task_failed(taskToken="not-a-correct-token") + ex.value.response["Error"]["Code"].should.equal("ValidationException") + ex.value.response["Error"]["Message"].should.equal("Invalid token") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + # RecordActivityTaskHeartbeat endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_record_activity_task_heartbeat(): conn = setup_workflow() @@ -194,6 +401,24 @@ def test_record_activity_task_heartbeat(): resp.should.equal({"cancelRequested": False}) +@mock_swf +def test_record_activity_task_heartbeat_boto3(): + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + activity_token = client.poll_for_activity_task( + domain="test-domain", taskList={"name": "activity-task-list"} + )["taskToken"] + + resp = client.record_activity_task_heartbeat(taskToken=activity_token) + resp.should.have.key("cancelRequested").equal(False) + + +# Has boto3 equivalent @mock_swf_deprecated def test_record_activity_task_heartbeat_with_wrong_token(): conn = setup_workflow() @@ -208,6 +433,27 @@ def test_record_activity_task_heartbeat_with_wrong_token(): ).should.throw(SWFResponseError) +@mock_swf +def test_record_activity_task_heartbeat_with_wrong_token_boto3(): + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + client.poll_for_activity_task( + domain="test-domain", taskList={"name": "activity-task-list"} + )["taskToken"] + + with pytest.raises(ClientError) as ex: + client.record_activity_task_heartbeat(taskToken="bad-token") + ex.value.response["Error"]["Code"].should.equal("ValidationException") + ex.value.response["Error"]["Message"].should.equal("Invalid token") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_record_activity_task_heartbeat_sets_details_in_case_of_timeout(): conn = setup_workflow() @@ -231,3 +477,34 @@ def test_record_activity_task_heartbeat_sets_details_in_case_of_timeout(): resp["events"][-2]["eventType"].should.equal("ActivityTaskTimedOut") attrs = resp["events"][-2]["activityTaskTimedOutEventAttributes"] attrs["details"].should.equal("some progress details") + + +@mock_swf +def test_record_activity_task_heartbeat_sets_details_in_case_of_timeout_boto3(): + if settings.TEST_SERVER_MODE: + raise SkipTest("Unable to manipulate time in ServerMode") + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + + with freeze_time("2015-01-01 12:00:00"): + activity_token = client.poll_for_activity_task( + domain="test-domain", taskList={"name": "activity-task-list"} + )["taskToken"] + client.record_activity_task_heartbeat( + taskToken=activity_token, details="some progress details" + ) + + with freeze_time("2015-01-01 12:05:30"): + # => Activity Task Heartbeat timeout reached!! + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + resp["events"][-2]["eventType"].should.equal("ActivityTaskTimedOut") + attrs = resp["events"][-2]["activityTaskTimedOutEventAttributes"] + attrs["details"].should.equal("some progress details") diff --git a/tests/test_swf/responses/test_activity_types.py b/tests/test_swf/responses/test_activity_types.py index d49e5d4cb..8e11ce778 100644 --- a/tests/test_swf/responses/test_activity_types.py +++ b/tests/test_swf/responses/test_activity_types.py @@ -2,6 +2,7 @@ import boto from boto.swf.exceptions import SWFResponseError import boto3 from botocore.exceptions import ClientError +import pytest import sure # noqa from moto import mock_swf_deprecated @@ -9,6 +10,7 @@ from moto import mock_swf # RegisterActivityType endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_register_activity_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -21,6 +23,26 @@ def test_register_activity_type(): actype["activityType"]["version"].should.equal("v1.0") +@mock_swf +def test_register_activity_type_boto3(): + client = boto3.client("swf", region_name="us-west-2") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", name="test-activity", version="v1.0" + ) + + types = client.list_activity_types( + domain="test-domain", registrationStatus="REGISTERED" + )["typeInfos"] + types.should.have.length_of(1) + actype = types[0] + actype["activityType"]["name"].should.equal("test-activity") + actype["activityType"]["version"].should.equal("v1.0") + + +# Has boto3 equivalent @mock_swf_deprecated def test_register_already_existing_activity_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -32,6 +54,28 @@ def test_register_already_existing_activity_type(): ).should.throw(SWFResponseError) +@mock_swf +def test_register_already_existing_activity_type_boto3(): + client = boto3.client("swf", region_name="us-west-2") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", name="test-activity", version="v1.0" + ) + + with pytest.raises(ClientError) as ex: + client.register_activity_type( + domain="test-domain", name="test-activity", version="v1.0" + ) + ex.value.response["Error"]["Code"].should.equal("TypeAlreadyExistsFault") + ex.value.response["Error"]["Message"].should.equal( + "ActivityType=[name=test-activity, version=v1.0]" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_register_with_wrong_parameter_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -43,6 +87,7 @@ def test_register_with_wrong_parameter_type(): # ListActivityTypes endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_list_activity_types(): conn = boto.connect_swf("the_key", "the_secret") @@ -59,6 +104,33 @@ def test_list_activity_types(): names.should.equal(["a-test-activity", "b-test-activity", "c-test-activity"]) +# ListActivityTypes endpoint +@mock_swf +def test_list_activity_types_boto3(): + client = boto3.client("swf", region_name="us-west-2") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", name="b-test-activity", version="v1.0" + ) + client.register_activity_type( + domain="test-domain", name="a-test-activity", version="v1.0" + ) + client.register_activity_type( + domain="test-domain", name="c-test-activity", version="v1.0" + ) + + types = client.list_activity_types( + domain="test-domain", registrationStatus="REGISTERED" + ) + names = [ + activity_type["activityType"]["name"] for activity_type in types["typeInfos"] + ] + names.should.equal(["a-test-activity", "b-test-activity", "c-test-activity"]) + + +# Has boto3 equivalent @mock_swf_deprecated def test_list_activity_types_reverse_order(): conn = boto.connect_swf("the_key", "the_secret") @@ -77,7 +149,34 @@ def test_list_activity_types_reverse_order(): names.should.equal(["c-test-activity", "b-test-activity", "a-test-activity"]) +@mock_swf +def test_list_activity_types_reverse_order_boto3(): + client = boto3.client("swf", region_name="us-west-2") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", name="b-test-activity", version="v1.0" + ) + client.register_activity_type( + domain="test-domain", name="a-test-activity", version="v1.0" + ) + client.register_activity_type( + domain="test-domain", name="c-test-activity", version="v1.0" + ) + + types = client.list_activity_types( + domain="test-domain", registrationStatus="REGISTERED", reverseOrder=True + ) + + names = [ + activity_type["activityType"]["name"] for activity_type in types["typeInfos"] + ] + names.should.equal(["c-test-activity", "b-test-activity", "a-test-activity"]) + + # DeprecateActivityType endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_deprecate_activity_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -91,6 +190,30 @@ def test_deprecate_activity_type(): actype["activityType"]["version"].should.equal("v1.0") +# DeprecateActivityType endpoint +@mock_swf +def test_deprecate_activity_type_boto3(): + client = boto3.client("swf", region_name="us-west-2") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", name="test-activity", version="v1.0" + ) + client.deprecate_activity_type( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ) + + types = client.list_activity_types( + domain="test-domain", registrationStatus="DEPRECATED" + ) + types.should.have.key("typeInfos").being.length_of(1) + actype = types["typeInfos"][0] + actype["activityType"]["name"].should.equal("test-activity") + actype["activityType"]["version"].should.equal("v1.0") + + +# Has boto3 equivalent @mock_swf_deprecated def test_deprecate_already_deprecated_activity_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -103,6 +226,32 @@ def test_deprecate_already_deprecated_activity_type(): ).should.throw(SWFResponseError) +@mock_swf +def test_deprecate_already_deprecated_activity_type_boto3(): + client = boto3.client("swf", region_name="us-west-2") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", name="test-activity", version="v1.0" + ) + client.deprecate_activity_type( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ) + + with pytest.raises(ClientError) as ex: + client.deprecate_activity_type( + domain="test-domain", + activityType={"name": "test-activity", "version": "v1.0"}, + ) + ex.value.response["Error"]["Code"].should.equal("TypeDeprecatedFault") + ex.value.response["Error"]["Message"].should.equal( + "ActivityType=[name=test-activity, version=v1.0]" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_deprecate_non_existent_activity_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -113,6 +262,25 @@ def test_deprecate_non_existent_activity_type(): ).should.throw(SWFResponseError) +@mock_swf +def test_deprecate_non_existent_activity_type_boto3(): + client = boto3.client("swf", region_name="us-west-2") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + + with pytest.raises(ClientError) as ex: + client.deprecate_activity_type( + domain="test-domain", + activityType={"name": "test-activity", "version": "v1.0"}, + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown type: ActivityType=[name=test-activity, version=v1.0]" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + # DeprecateActivityType endpoint @mock_swf def test_undeprecate_activity_type(): @@ -185,6 +353,7 @@ def test_undeprecate_non_existent_activity_type(): # DescribeActivityType endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_describe_activity_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -205,6 +374,31 @@ def test_describe_activity_type(): infos["status"].should.equal("REGISTERED") +@mock_swf +def test_describe_activity_type_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", + name="test-activity", + version="v1.0", + defaultTaskList={"name": "foo"}, + defaultTaskHeartbeatTimeout="32", + ) + + actype = client.describe_activity_type( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ) + actype["configuration"]["defaultTaskList"]["name"].should.equal("foo") + infos = actype["typeInfo"] + infos["activityType"]["name"].should.equal("test-activity") + infos["activityType"]["version"].should.equal("v1.0") + infos["status"].should.equal("REGISTERED") + + +# Has boto3 equivalent @mock_swf_deprecated def test_describe_non_existent_activity_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -213,3 +407,22 @@ def test_describe_non_existent_activity_type(): conn.describe_activity_type.when.called_with( "test-domain", "non-existent", "v1.0" ).should.throw(SWFResponseError) + + +@mock_swf +def test_describe_non_existent_activity_type_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + + with pytest.raises(ClientError) as ex: + client.describe_activity_type( + domain="test-domain", + activityType={"name": "test-activity", "version": "v1.0"}, + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown type: ActivityType=[name=test-activity, version=v1.0]" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) diff --git a/tests/test_swf/responses/test_decision_tasks.py b/tests/test_swf/responses/test_decision_tasks.py index 6493302f9..944f0a10e 100644 --- a/tests/test_swf/responses/test_decision_tasks.py +++ b/tests/test_swf/responses/test_decision_tasks.py @@ -1,14 +1,18 @@ from boto.swf.exceptions import SWFResponseError +from botocore.exceptions import ClientError +from datetime import datetime from freezegun import freeze_time +import pytest import sure # noqa -from moto import mock_swf_deprecated +from moto import mock_swf_deprecated, mock_swf, settings from moto.swf import swf_backend -from ..utils import setup_workflow +from ..utils import setup_workflow, setup_workflow_boto3 # PollForDecisionTask endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_poll_for_decision_task_when_one(): conn = setup_workflow() @@ -30,6 +34,31 @@ def test_poll_for_decision_task_when_one(): ) +@mock_swf +def test_poll_for_decision_task_when_one_boto3(): + client = setup_workflow_boto3() + + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + types = [evt["eventType"] for evt in resp["events"]] + types.should.equal(["WorkflowExecutionStarted", "DecisionTaskScheduled"]) + + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"}, identity="srv01" + ) + types = [evt["eventType"] for evt in resp["events"]] + types.should.equal( + ["WorkflowExecutionStarted", "DecisionTaskScheduled", "DecisionTaskStarted"] + ) + + resp["events"][-1]["decisionTaskStartedEventAttributes"]["identity"].should.equal( + "srv01" + ) + + +# Has boto3 equivalent @mock_swf_deprecated def test_poll_for_decision_task_previous_started_event_id(): conn = setup_workflow() @@ -54,6 +83,37 @@ def test_poll_for_decision_task_previous_started_event_id(): assert resp["previousStartedEventId"] == 3 +@mock_swf +def test_poll_for_decision_task_previous_started_event_id_boto3(): + client = setup_workflow_boto3() + + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + assert resp["workflowExecution"]["runId"] == client.run_id + assert "previousStartedEventId" not in resp + + # Require a failing decision, in this case a non-existant activity type + attrs = { + "activityId": "spam", + "activityType": {"name": "test-activity", "version": "v1.42"}, + "taskList": {"name": "eggs"}, + } + decision = { + "decisionType": "ScheduleActivityTask", + "scheduleActivityTaskDecisionAttributes": attrs, + } + client.respond_decision_task_completed( + taskToken=resp["taskToken"], decisions=[decision] + ) + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + assert resp["workflowExecution"]["runId"] == client.run_id + assert resp["previousStartedEventId"] == 3 + + +# Has boto3 equivalent @mock_swf_deprecated def test_poll_for_decision_task_when_none(): conn = setup_workflow() @@ -65,6 +125,22 @@ def test_poll_for_decision_task_when_none(): resp.should.equal({"previousStartedEventId": 0, "startedEventId": 0}) +@mock_swf +def test_poll_for_decision_task_when_none_boto3(): + client = setup_workflow_boto3() + + client.poll_for_decision_task(domain="test-domain", taskList={"name": "queue"}) + + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + # this is the DecisionTask representation you get from the real SWF + # after waiting 60s when there's no decision to be taken + resp.should.have.key("previousStartedEventId").equal(0) + resp.should.have.key("startedEventId").equal(0) + + +# Has boto3 equivalent @mock_swf_deprecated def test_poll_for_decision_task_on_non_existent_queue(): conn = setup_workflow() @@ -72,6 +148,17 @@ def test_poll_for_decision_task_on_non_existent_queue(): resp.should.equal({"previousStartedEventId": 0, "startedEventId": 0}) +@mock_swf +def test_poll_for_decision_task_on_non_existent_queue_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "non-existent-queue"} + ) + resp.should.have.key("previousStartedEventId").equal(0) + resp.should.have.key("startedEventId").equal(0) + + +# Has boto3 equivalent @mock_swf_deprecated def test_poll_for_decision_task_with_reverse_order(): conn = setup_workflow() @@ -82,7 +169,20 @@ def test_poll_for_decision_task_with_reverse_order(): ) +@mock_swf +def test_poll_for_decision_task_with_reverse_order_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"}, reverseOrder=True + ) + types = [evt["eventType"] for evt in resp["events"]] + types.should.equal( + ["DecisionTaskStarted", "DecisionTaskScheduled", "WorkflowExecutionStarted"] + ) + + # CountPendingDecisionTasks endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_count_pending_decision_tasks(): conn = setup_workflow() @@ -91,6 +191,18 @@ def test_count_pending_decision_tasks(): resp.should.equal({"count": 1, "truncated": False}) +@mock_swf +def test_count_pending_decision_tasks_boto3(): + client = setup_workflow_boto3() + client.poll_for_decision_task(domain="test-domain", taskList={"name": "queue"}) + resp = client.count_pending_decision_tasks( + domain="test-domain", taskList={"name": "queue"} + ) + resp.should.have.key("count").equal(1) + resp.should.have.key("truncated").equal(False) + + +# Has boto3 equivalent @mock_swf_deprecated def test_count_pending_decision_tasks_on_non_existent_task_list(): conn = setup_workflow() @@ -98,6 +210,17 @@ def test_count_pending_decision_tasks_on_non_existent_task_list(): resp.should.equal({"count": 0, "truncated": False}) +@mock_swf +def test_count_pending_decision_tasks_on_non_existent_task_list_boto3(): + client = setup_workflow_boto3() + resp = client.count_pending_decision_tasks( + domain="test-domain", taskList={"name": "non-existent"} + ) + resp.should.have.key("count").equal(0) + resp.should.have.key("truncated").equal(False) + + +# Has boto3 equivalent @mock_swf_deprecated def test_count_pending_decision_tasks_after_decision_completes(): conn = setup_workflow() @@ -108,7 +231,23 @@ def test_count_pending_decision_tasks_after_decision_completes(): resp.should.equal({"count": 0, "truncated": False}) +@mock_swf +def test_count_pending_decision_tasks_after_decision_completes_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + client.respond_decision_task_completed(taskToken=resp["taskToken"]) + + resp = client.count_pending_decision_tasks( + domain="test-domain", taskList={"name": "queue"} + ) + resp.should.have.key("count").equal(0) + resp.should.have.key("truncated").equal(False) + + # RespondDecisionTaskCompleted endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_with_no_decision(): conn = setup_workflow() @@ -146,6 +285,48 @@ def test_respond_decision_task_completed_with_no_decision(): resp["latestExecutionContext"].should.equal("free-form context") +@mock_swf +def test_respond_decision_task_completed_with_no_decision_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + task_token = resp["taskToken"] + + client.respond_decision_task_completed( + taskToken=task_token, executionContext="free-form context" + ) + + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + types = [evt["eventType"] for evt in resp["events"]] + types.should.equal( + [ + "WorkflowExecutionStarted", + "DecisionTaskScheduled", + "DecisionTaskStarted", + "DecisionTaskCompleted", + ] + ) + evt = resp["events"][-1] + evt["decisionTaskCompletedEventAttributes"].should.equal( + { + "executionContext": "free-form context", + "scheduledEventId": 2, + "startedEventId": 3, + } + ) + + resp = client.describe_workflow_execution( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + resp["latestExecutionContext"].should.equal("free-form context") + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_with_wrong_token(): conn = setup_workflow() @@ -155,6 +336,18 @@ def test_respond_decision_task_completed_with_wrong_token(): ).should.throw(SWFResponseError) +@mock_swf +def test_respond_decision_task_completed_with_wrong_token_boto3(): + client = setup_workflow_boto3() + client.poll_for_decision_task(domain="test-domain", taskList={"name": "queue"}) + with pytest.raises(ClientError) as ex: + client.respond_decision_task_completed(taskToken="not-a-correct-token") + ex.value.response["Error"]["Code"].should.equal("ValidationException") + ex.value.response["Error"]["Message"].should.equal("Invalid token") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_on_close_workflow_execution(): conn = setup_workflow() @@ -172,6 +365,28 @@ def test_respond_decision_task_completed_on_close_workflow_execution(): ) +@mock_swf +def test_respond_decision_task_completed_on_close_workflow_execution_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + task_token = resp["taskToken"] + + client.terminate_workflow_execution(domain="test-domain", workflowId="uid-abcd1234") + + with pytest.raises(ClientError) as ex: + client.respond_decision_task_completed(taskToken=task_token) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown execution: WorkflowExecution=[workflowId=uid-abcd1234, runId={}]".format( + client.run_id + ) + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_with_task_already_completed(): conn = setup_workflow() @@ -184,6 +399,25 @@ def test_respond_decision_task_completed_with_task_already_completed(): ) +@mock_swf +def test_respond_decision_task_completed_with_task_already_completed_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + task_token = resp["taskToken"] + client.respond_decision_task_completed(taskToken=task_token) + + with pytest.raises(ClientError) as ex: + client.respond_decision_task_completed(taskToken=task_token) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown decision task, scheduledEventId = 2" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_with_complete_workflow_execution(): conn = setup_workflow() @@ -217,6 +451,42 @@ def test_respond_decision_task_completed_with_complete_workflow_execution(): ].should.equal("foo bar") +@mock_swf +def test_respond_decision_task_completed_with_complete_workflow_execution_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + task_token = resp["taskToken"] + + decisions = [ + { + "decisionType": "CompleteWorkflowExecution", + "completeWorkflowExecutionDecisionAttributes": {"result": "foo bar"}, + } + ] + client.respond_decision_task_completed(taskToken=task_token, decisions=decisions) + + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + types = [evt["eventType"] for evt in resp["events"]] + types.should.equal( + [ + "WorkflowExecutionStarted", + "DecisionTaskScheduled", + "DecisionTaskStarted", + "DecisionTaskCompleted", + "WorkflowExecutionCompleted", + ] + ) + resp["events"][-1]["workflowExecutionCompletedEventAttributes"][ + "result" + ].should.equal("foo bar") + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_with_close_decision_not_last(): conn = setup_workflow() @@ -233,6 +503,31 @@ def test_respond_decision_task_completed_with_close_decision_not_last(): ).should.throw(SWFResponseError, r"Close must be last decision in list") +@mock_swf +def test_respond_decision_task_completed_with_close_decision_not_last_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + task_token = resp["taskToken"] + + decisions = [ + {"decisionType": "CompleteWorkflowExecution"}, + {"decisionType": "WeDontCare"}, + ] + + with pytest.raises(ClientError) as ex: + client.respond_decision_task_completed( + taskToken=task_token, decisions=decisions + ) + ex.value.response["Error"]["Code"].should.equal("ValidationException") + ex.value.response["Error"]["Message"].should.equal( + "Close must be last decision in list" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_with_invalid_decision_type(): conn = setup_workflow() @@ -252,6 +547,31 @@ def test_respond_decision_task_completed_with_invalid_decision_type(): ) +@mock_swf +def test_respond_decision_task_completed_with_invalid_decision_type_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + task_token = resp["taskToken"] + + decisions = [ + {"decisionType": "BadDecisionType"}, + {"decisionType": "CompleteWorkflowExecution"}, + ] + + with pytest.raises(ClientError) as ex: + client.respond_decision_task_completed( + taskToken=task_token, decisions=decisions + ) + ex.value.response["Error"]["Code"].should.equal("ValidationException") + ex.value.response["Error"]["Message"].should.match( + "Value 'BadDecisionType' at 'decisions.1.member.decisionType'" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_with_missing_attributes(): conn = setup_workflow() @@ -274,6 +594,7 @@ def test_respond_decision_task_completed_with_missing_attributes(): ) +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_with_missing_attributes_totally(): conn = setup_workflow() @@ -291,6 +612,28 @@ def test_respond_decision_task_completed_with_missing_attributes_totally(): ) +@mock_swf +def test_respond_decision_task_completed_with_missing_attributes_totally_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + task_token = resp["taskToken"] + + decisions = [{"decisionType": "StartTimer"}] + + with pytest.raises(ClientError) as ex: + client.respond_decision_task_completed( + taskToken=task_token, decisions=decisions + ) + ex.value.response["Error"]["Code"].should.equal("ValidationException") + ex.value.response["Error"]["Message"].should.match( + "Value null at 'decisions.1.member.startTimerDecisionAttributes.timerId' failed to satisfy constraint" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_respond_decision_task_completed_with_fail_workflow_execution(): conn = setup_workflow() @@ -327,6 +670,45 @@ def test_respond_decision_task_completed_with_fail_workflow_execution(): attrs["details"].should.equal("foo") +@mock_swf +def test_respond_decision_task_completed_with_fail_workflow_execution_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + task_token = resp["taskToken"] + + decisions = [ + { + "decisionType": "FailWorkflowExecution", + "failWorkflowExecutionDecisionAttributes": { + "reason": "my rules", + "details": "foo", + }, + } + ] + client.respond_decision_task_completed(taskToken=task_token, decisions=decisions) + + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + types = [evt["eventType"] for evt in resp["events"]] + types.should.equal( + [ + "WorkflowExecutionStarted", + "DecisionTaskScheduled", + "DecisionTaskStarted", + "DecisionTaskCompleted", + "WorkflowExecutionFailed", + ] + ) + attrs = resp["events"][-1]["workflowExecutionFailedEventAttributes"] + attrs["reason"].should.equal("my rules") + attrs["details"].should.equal("foo") + + +# Has boto3 equivalent @mock_swf_deprecated @freeze_time("2015-01-01 12:00:00") def test_respond_decision_task_completed_with_schedule_activity_task(): @@ -375,3 +757,61 @@ def test_respond_decision_task_completed_with_schedule_activity_task(): resp = conn.describe_workflow_execution("test-domain", conn.run_id, "uid-abcd1234") resp["latestActivityTaskTimestamp"].should.equal(1420113600.0) + + +@mock_swf +@freeze_time("2015-01-01 12:00:00") +def test_respond_decision_task_completed_with_schedule_activity_task_boto3(): + client = setup_workflow_boto3() + resp = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + ) + task_token = resp["taskToken"] + + decisions = [ + { + "decisionType": "ScheduleActivityTask", + "scheduleActivityTaskDecisionAttributes": { + "activityId": "my-activity-001", + "activityType": {"name": "test-activity", "version": "v1.1"}, + "heartbeatTimeout": "60", + "input": "123", + "taskList": {"name": "my-task-list"}, + }, + } + ] + client.respond_decision_task_completed(taskToken=task_token, decisions=decisions) + + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + types = [evt["eventType"] for evt in resp["events"]] + types.should.equal( + [ + "WorkflowExecutionStarted", + "DecisionTaskScheduled", + "DecisionTaskStarted", + "DecisionTaskCompleted", + "ActivityTaskScheduled", + ] + ) + resp["events"][-1]["activityTaskScheduledEventAttributes"].should.equal( + { + "decisionTaskCompletedEventId": 4, + "activityId": "my-activity-001", + "activityType": {"name": "test-activity", "version": "v1.1"}, + "heartbeatTimeout": "60", + "input": "123", + "taskList": {"name": "my-task-list"}, + } + ) + + resp = client.describe_workflow_execution( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + resp["latestActivityTaskTimestamp"].should.be.a(datetime) + if not settings.TEST_SERVER_MODE: + ts = resp["latestActivityTaskTimestamp"].strftime("%Y-%m-%d %H:%M:%S") + ts.should.equal("2015-01-01 12:00:00") diff --git a/tests/test_swf/responses/test_domains.py b/tests/test_swf/responses/test_domains.py index fcf7da975..e0c70ce19 100644 --- a/tests/test_swf/responses/test_domains.py +++ b/tests/test_swf/responses/test_domains.py @@ -2,6 +2,7 @@ import boto from boto.swf.exceptions import SWFResponseError import boto3 from botocore.exceptions import ClientError +import pytest import sure # noqa from moto import mock_swf_deprecated @@ -10,6 +11,7 @@ from moto.core import ACCOUNT_ID # RegisterDomain endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_register_domain(): conn = boto.connect_swf("the_key", "the_secret") @@ -26,6 +28,28 @@ def test_register_domain(): ) +@mock_swf +def test_register_domain_boto3(): + client = boto3.client("swf", region_name="us-west-1") + client.register_domain( + name="test-domain", + workflowExecutionRetentionPeriodInDays="60", + description="A test domain", + ) + + all_domains = client.list_domains(registrationStatus="REGISTERED") + all_domains.should.have.key("domainInfos").being.length_of(1) + domain = all_domains["domainInfos"][0] + + domain["name"].should.equal("test-domain") + domain["status"].should.equal("REGISTERED") + domain["description"].should.equal("A test domain") + domain["arn"].should.equal( + "arn:aws:swf:us-west-1:{0}:/domain/test-domain".format(ACCOUNT_ID) + ) + + +# Has boto3 equivalent @mock_swf_deprecated def test_register_already_existing_domain(): conn = boto.connect_swf("the_key", "the_secret") @@ -36,6 +60,27 @@ def test_register_already_existing_domain(): ).should.throw(SWFResponseError) +@mock_swf +def test_register_already_existing_domain_boto3(): + client = boto3.client("swf", region_name="us-west-1") + client.register_domain( + name="test-domain", + workflowExecutionRetentionPeriodInDays="60", + description="A test domain", + ) + + with pytest.raises(ClientError) as ex: + client.register_domain( + name="test-domain", + workflowExecutionRetentionPeriodInDays="60", + description="A test domain", + ) + ex.value.response["Error"]["Code"].should.equal("DomainAlreadyExistsFault") + ex.value.response["Error"]["Message"].should.equal("test-domain") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_register_with_wrong_parameter_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -46,6 +91,7 @@ def test_register_with_wrong_parameter_type(): # ListDomains endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_list_domains_order(): conn = boto.connect_swf("the_key", "the_secret") @@ -58,6 +104,27 @@ def test_list_domains_order(): names.should.equal(["a-test-domain", "b-test-domain", "c-test-domain"]) +@mock_swf +def test_list_domains_order_boto3(): + client = boto3.client("swf", region_name="us-west-1") + client.register_domain( + name="b-test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_domain( + name="a-test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_domain( + name="c-test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + + all_domains = client.list_domains(registrationStatus="REGISTERED") + all_domains.should.have.key("domainInfos").being.length_of(3) + + names = [domain["name"] for domain in all_domains["domainInfos"]] + names.should.equal(["a-test-domain", "b-test-domain", "c-test-domain"]) + + +# Has boto3 equivalent @mock_swf_deprecated def test_list_domains_reverse_order(): conn = boto.connect_swf("the_key", "the_secret") @@ -70,7 +137,30 @@ def test_list_domains_reverse_order(): names.should.equal(["c-test-domain", "b-test-domain", "a-test-domain"]) +@mock_swf +def test_list_domains_reverse_order_boto3(): + client = boto3.client("swf", region_name="us-west-1") + client.register_domain( + name="b-test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_domain( + name="a-test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_domain( + name="c-test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + + all_domains = client.list_domains( + registrationStatus="REGISTERED", reverseOrder=True + ) + all_domains.should.have.key("domainInfos").being.length_of(3) + + names = [domain["name"] for domain in all_domains["domainInfos"]] + names.should.equal(["c-test-domain", "b-test-domain", "a-test-domain"]) + + # DeprecateDomain endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_deprecate_domain(): conn = boto.connect_swf("the_key", "the_secret") @@ -83,6 +173,25 @@ def test_deprecate_domain(): domain["name"].should.equal("test-domain") +@mock_swf +def test_deprecate_domain_boto3(): + client = boto3.client("swf", region_name="us-west-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.deprecate_domain(name="test-domain") + + all_domains = client.list_domains(registrationStatus="REGISTERED") + all_domains.should.have.key("domainInfos").being.length_of(0) + + all_domains = client.list_domains(registrationStatus="DEPRECATED") + all_domains.should.have.key("domainInfos").being.length_of(1) + + domain = all_domains["domainInfos"][0] + domain["name"].should.equal("test-domain") + + +# Has boto3 equivalent @mock_swf_deprecated def test_deprecate_already_deprecated_domain(): conn = boto.connect_swf("the_key", "the_secret") @@ -92,6 +201,22 @@ def test_deprecate_already_deprecated_domain(): conn.deprecate_domain.when.called_with("test-domain").should.throw(SWFResponseError) +@mock_swf +def test_deprecate_already_deprecated_domain_boto3(): + client = boto3.client("swf", region_name="us-west-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.deprecate_domain(name="test-domain") + + with pytest.raises(ClientError) as ex: + client.deprecate_domain(name="test-domain") + ex.value.response["Error"]["Code"].should.equal("DomainDeprecatedFault") + ex.value.response["Error"]["Message"].should.equal("test-domain") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_deprecate_non_existent_domain(): conn = boto.connect_swf("the_key", "the_secret") @@ -101,6 +226,17 @@ def test_deprecate_non_existent_domain(): ) +@mock_swf +def test_deprecate_non_existent_domain_boto3(): + client = boto3.client("swf", region_name="us-west-1") + + with pytest.raises(ClientError) as ex: + client.deprecate_domain(name="non-existent") + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal("Unknown domain: non-existent") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + # UndeprecateDomain endpoint @mock_swf def test_undeprecate_domain(): @@ -152,6 +288,7 @@ def test_undeprecate_non_existent_domain(): # DescribeDomain endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_describe_domain(): conn = boto.connect_swf("the_key", "the_secret") @@ -164,8 +301,36 @@ def test_describe_domain(): domain["domainInfo"]["status"].should.equal("REGISTERED") +@mock_swf +def test_describe_domain_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", + workflowExecutionRetentionPeriodInDays="60", + description="A test domain", + ) + + domain = client.describe_domain(name="test-domain") + domain["configuration"]["workflowExecutionRetentionPeriodInDays"].should.equal("60") + domain["domainInfo"]["description"].should.equal("A test domain") + domain["domainInfo"]["name"].should.equal("test-domain") + domain["domainInfo"]["status"].should.equal("REGISTERED") + + +# Has boto3 equivalent @mock_swf_deprecated def test_describe_non_existent_domain(): conn = boto.connect_swf("the_key", "the_secret") conn.describe_domain.when.called_with("non-existent").should.throw(SWFResponseError) + + +@mock_swf +def test_describe_non_existent_domain_boto3(): + client = boto3.client("swf", region_name="us-west-1") + + with pytest.raises(ClientError) as ex: + client.describe_domain(name="non-existent") + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal("Unknown domain: non-existent") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) diff --git a/tests/test_swf/responses/test_timeouts.py b/tests/test_swf/responses/test_timeouts.py index 25ca8ae7d..b26c3ea92 100644 --- a/tests/test_swf/responses/test_timeouts.py +++ b/tests/test_swf/responses/test_timeouts.py @@ -1,13 +1,17 @@ +from datetime import datetime from freezegun import freeze_time +from unittest import SkipTest import sure # noqa -from moto import mock_swf_deprecated +from moto import mock_swf_deprecated, mock_swf, settings from ..utils import setup_workflow, SCHEDULE_ACTIVITY_TASK_DECISION +from ..utils import setup_workflow_boto3 # Activity Task Heartbeat timeout # Default value in workflow helpers: 5 mins +# Has boto3 equivalent @mock_swf_deprecated def test_activity_task_heartbeat_timeout(): with freeze_time("2015-01-01 12:00:00"): @@ -43,8 +47,52 @@ def test_activity_task_heartbeat_timeout(): resp["events"][-1]["eventType"].should.equal("DecisionTaskScheduled") +# Activity Task Heartbeat timeout +# Default value in workflow helpers: 5 mins +@mock_swf +def test_activity_task_heartbeat_timeout_boto3(): + if settings.TEST_SERVER_MODE: + raise SkipTest("Unable to manipulate time in ServerMode") + with freeze_time("2015-01-01 12:00:00"): + client = setup_workflow_boto3() + decision_token = client.poll_for_decision_task( + domain="test-domain", taskList={"name": "queue"} + )["taskToken"] + client.respond_decision_task_completed( + taskToken=decision_token, decisions=[SCHEDULE_ACTIVITY_TASK_DECISION] + ) + client.poll_for_activity_task( + domain="test-domain", + taskList={"name": "activity-task-list"}, + identity="surprise", + ) + + with freeze_time("2015-01-01 12:04:30"): + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + resp["events"][-1]["eventType"].should.equal("ActivityTaskStarted") + + with freeze_time("2015-01-01 12:05:30"): + # => Activity Task Heartbeat timeout reached!! + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + + resp["events"][-2]["eventType"].should.equal("ActivityTaskTimedOut") + attrs = resp["events"][-2]["activityTaskTimedOutEventAttributes"] + attrs["timeoutType"].should.equal("HEARTBEAT") + # checks that event has been emitted at 12:05:00, not 12:05:30 + resp["events"][-2]["eventTimestamp"].should.be.a(datetime) + ts = resp["events"][-2]["eventTimestamp"].strftime("%Y-%m-%d %H:%M:%S") + ts.should.equal("2015-01-01 12:05:00") + + # Decision Task Start to Close timeout # Default value in workflow helpers: 5 mins +# Has boto3 equivalent @mock_swf_deprecated def test_decision_task_start_to_close_timeout(): pass @@ -90,8 +138,62 @@ def test_decision_task_start_to_close_timeout(): resp["events"][-2]["eventTimestamp"].should.equal(1420113900.0) +# Decision Task Start to Close timeout +# Default value in workflow helpers: 5 mins +@mock_swf +def test_decision_task_start_to_close_timeout_boto3(): + if settings.TEST_SERVER_MODE: + raise SkipTest("Unable to manipulate time in ServerMode") + + with freeze_time("2015-01-01 12:00:00"): + client = setup_workflow_boto3() + client.poll_for_decision_task(domain="test-domain", taskList={"name": "queue"}) + + with freeze_time("2015-01-01 12:04:30"): + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + + event_types = [evt["eventType"] for evt in resp["events"]] + event_types.should.equal( + ["WorkflowExecutionStarted", "DecisionTaskScheduled", "DecisionTaskStarted"] + ) + + with freeze_time("2015-01-01 12:05:30"): + # => Decision Task Start to Close timeout reached!! + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + + event_types = [evt["eventType"] for evt in resp["events"]] + event_types.should.equal( + [ + "WorkflowExecutionStarted", + "DecisionTaskScheduled", + "DecisionTaskStarted", + "DecisionTaskTimedOut", + "DecisionTaskScheduled", + ] + ) + attrs = resp["events"][-2]["decisionTaskTimedOutEventAttributes"] + attrs.should.equal( + { + "scheduledEventId": 2, + "startedEventId": 3, + "timeoutType": "START_TO_CLOSE", + } + ) + # checks that event has been emitted at 12:05:00, not 12:05:30 + resp["events"][-2]["eventTimestamp"].should.be.a(datetime) + ts = resp["events"][-2]["eventTimestamp"].strftime("%Y-%m-%d %H:%M:%S") + ts.should.equal("2015-01-01 12:05:00") + + # Workflow Execution Start to Close timeout # Default value in workflow helpers: 2 hours +# Has boto3 equivalent @mock_swf_deprecated def test_workflow_execution_start_to_close_timeout(): pass @@ -124,3 +226,44 @@ def test_workflow_execution_start_to_close_timeout(): attrs.should.equal({"childPolicy": "ABANDON", "timeoutType": "START_TO_CLOSE"}) # checks that event has been emitted at 14:00:00, not 14:00:30 resp["events"][-1]["eventTimestamp"].should.equal(1420120800.0) + + +# Workflow Execution Start to Close timeout +# Default value in workflow helpers: 2 hours +@mock_swf +def test_workflow_execution_start_to_close_timeout_boto3(): + if settings.TEST_SERVER_MODE: + raise SkipTest("Unable to manipulate time in ServerMode") + with freeze_time("2015-01-01 12:00:00"): + client = setup_workflow_boto3() + + with freeze_time("2015-01-01 13:59:30"): + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + + event_types = [evt["eventType"] for evt in resp["events"]] + event_types.should.equal(["WorkflowExecutionStarted", "DecisionTaskScheduled"]) + + with freeze_time("2015-01-01 14:00:30"): + # => Workflow Execution Start to Close timeout reached!! + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": client.run_id, "workflowId": "uid-abcd1234"}, + ) + + event_types = [evt["eventType"] for evt in resp["events"]] + event_types.should.equal( + [ + "WorkflowExecutionStarted", + "DecisionTaskScheduled", + "WorkflowExecutionTimedOut", + ] + ) + attrs = resp["events"][-1]["workflowExecutionTimedOutEventAttributes"] + attrs.should.equal({"childPolicy": "ABANDON", "timeoutType": "START_TO_CLOSE"}) + # checks that event has been emitted at 14:00:00, not 14:00:30 + resp["events"][-1]["eventTimestamp"].should.be.a(datetime) + ts = resp["events"][-1]["eventTimestamp"].strftime("%Y-%m-%d %H:%M:%S") + ts.should.equal("2015-01-01 14:00:00") diff --git a/tests/test_swf/responses/test_workflow_executions.py b/tests/test_swf/responses/test_workflow_executions.py index 2832abf75..fb94cf63c 100644 --- a/tests/test_swf/responses/test_workflow_executions.py +++ b/tests/test_swf/responses/test_workflow_executions.py @@ -1,10 +1,13 @@ import boto +import boto3 from boto.swf.exceptions import SWFResponseError +from botocore.exceptions import ClientError from datetime import datetime, timedelta +import pytest import sure # noqa -from moto import mock_swf_deprecated +from moto import mock_swf_deprecated, mock_swf from moto.core.utils import unix_time @@ -26,7 +29,30 @@ def setup_swf_environment(): return conn +def setup_swf_environment_boto3(): + client = boto3.client("swf", region_name="us-west-1") + client.register_domain( + name="test-domain", + workflowExecutionRetentionPeriodInDays="60", + description="A test domain", + ) + client.register_workflow_type( + domain="test-domain", + name="test-workflow", + version="v1.0", + defaultTaskList={"name": "queue"}, + defaultChildPolicy="TERMINATE", + defaultTaskStartToCloseTimeout="300", + defaultExecutionStartToCloseTimeout="300", + ) + client.register_activity_type( + domain="test-domain", name="test-activity", version="v1.1" + ) + return client + + # StartWorkflowExecution endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_start_workflow_execution(): conn = setup_swf_environment() @@ -37,6 +63,20 @@ def test_start_workflow_execution(): wf.should.contain("runId") +# StartWorkflowExecution endpoint +@mock_swf +def test_start_workflow_execution_boto3(): + client = setup_swf_environment_boto3() + + wf = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + wf.should.have.key("runId") + + +# Has boto3 equivalent @mock_swf_deprecated def test_signal_workflow_execution(): conn = setup_swf_environment() @@ -54,6 +94,32 @@ def test_signal_workflow_execution(): wfe["openCounts"]["openDecisionTasks"].should.equal(2) +@mock_swf +def test_signal_workflow_execution_boto3(): + client = setup_swf_environment_boto3() + hsh = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + run_id = hsh["runId"] + + wfe = client.signal_workflow_execution( + domain="test-domain", + signalName="my_signal", + workflowId="uid-abcd1234", + input="my_input", + runId=run_id, + ) + + wfe = client.describe_workflow_execution( + domain="test-domain", execution={"runId": run_id, "workflowId": "uid-abcd1234"} + ) + + wfe["openCounts"]["openDecisionTasks"].should.equal(2) + + +# Has boto3 equivalent @mock_swf_deprecated def test_start_already_started_workflow_execution(): conn = setup_swf_environment() @@ -66,6 +132,29 @@ def test_start_already_started_workflow_execution(): ).should.throw(SWFResponseError) +@mock_swf +def test_start_already_started_workflow_execution_boto3(): + client = setup_swf_environment_boto3() + client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + + with pytest.raises(ClientError) as ex: + client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + ex.value.response["Error"]["Code"].should.equal( + "WorkflowExecutionAlreadyStartedFault" + ) + ex.value.response["Error"]["Message"].should.equal("Already Started") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + +# Has boto3 equivalent @mock_swf_deprecated def test_start_workflow_execution_on_deprecated_type(): conn = setup_swf_environment() @@ -76,7 +165,28 @@ def test_start_workflow_execution_on_deprecated_type(): ).should.throw(SWFResponseError) +@mock_swf +def test_start_workflow_execution_on_deprecated_type_boto3(): + client = setup_swf_environment_boto3() + client.deprecate_workflow_type( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ) + + with pytest.raises(ClientError) as ex: + client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + ex.value.response["Error"]["Code"].should.equal("TypeDeprecatedFault") + ex.value.response["Error"]["Message"].should.equal( + "WorkflowType=[name=test-workflow, version=v1.0]" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + # DescribeWorkflowExecution endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_describe_workflow_execution(): conn = setup_swf_environment() @@ -90,6 +200,25 @@ def test_describe_workflow_execution(): wfe["executionInfo"]["executionStatus"].should.equal("OPEN") +# DescribeWorkflowExecution endpoint +@mock_swf +def test_describe_workflow_execution_boto3(): + client = setup_swf_environment_boto3() + hsh = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + run_id = hsh["runId"] + + wfe = client.describe_workflow_execution( + domain="test-domain", execution={"runId": run_id, "workflowId": "uid-abcd1234"} + ) + wfe["executionInfo"]["execution"]["workflowId"].should.equal("uid-abcd1234") + wfe["executionInfo"]["executionStatus"].should.equal("OPEN") + + +# Has boto3 equivalent @mock_swf_deprecated def test_describe_non_existent_workflow_execution(): conn = setup_swf_environment() @@ -99,7 +228,24 @@ def test_describe_non_existent_workflow_execution(): ).should.throw(SWFResponseError) +@mock_swf +def test_describe_non_existent_workflow_execution_boto3(): + client = setup_swf_environment_boto3() + + with pytest.raises(ClientError) as ex: + client.describe_workflow_execution( + domain="test-domain", + execution={"runId": "wrong-run-id", "workflowId": "uid-abcd1234"}, + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown execution: WorkflowExecution=[workflowId=uid-abcd1234, runId=wrong-run-id]" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + # GetWorkflowExecutionHistory endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_get_workflow_execution_history(): conn = setup_swf_environment() @@ -113,6 +259,25 @@ def test_get_workflow_execution_history(): types.should.equal(["WorkflowExecutionStarted", "DecisionTaskScheduled"]) +# GetWorkflowExecutionHistory endpoint +@mock_swf +def test_get_workflow_execution_history_boto3(): + client = setup_swf_environment_boto3() + hsh = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + run_id = hsh["runId"] + + resp = client.get_workflow_execution_history( + domain="test-domain", execution={"runId": run_id, "workflowId": "uid-abcd1234"}, + ) + types = [evt["eventType"] for evt in resp["events"]] + types.should.equal(["WorkflowExecutionStarted", "DecisionTaskScheduled"]) + + +# Has boto3 equivalent @mock_swf_deprecated def test_get_workflow_execution_history_with_reverse_order(): conn = setup_swf_environment() @@ -128,6 +293,26 @@ def test_get_workflow_execution_history_with_reverse_order(): types.should.equal(["DecisionTaskScheduled", "WorkflowExecutionStarted"]) +@mock_swf +def test_get_workflow_execution_history_with_reverse_order_boto3(): + client = setup_swf_environment_boto3() + hsh = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + run_id = hsh["runId"] + + resp = client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": run_id, "workflowId": "uid-abcd1234"}, + reverseOrder=True, + ) + types = [evt["eventType"] for evt in resp["events"]] + types.should.equal(["DecisionTaskScheduled", "WorkflowExecutionStarted"]) + + +# Has boto3 equivalent @mock_swf_deprecated def test_get_workflow_execution_history_on_non_existent_workflow_execution(): conn = setup_swf_environment() @@ -137,7 +322,24 @@ def test_get_workflow_execution_history_on_non_existent_workflow_execution(): ).should.throw(SWFResponseError) +@mock_swf +def test_get_workflow_execution_history_on_non_existent_workflow_execution_boto3(): + client = setup_swf_environment_boto3() + + with pytest.raises(ClientError) as ex: + client.get_workflow_execution_history( + domain="test-domain", + execution={"runId": "wrong-run-id", "workflowId": "wrong-workflow-id"}, + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown execution: WorkflowExecution=[workflowId=wrong-workflow-id, runId=wrong-run-id]" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + + # ListOpenWorkflowExecutions endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_list_open_workflow_executions(): conn = setup_swf_environment() @@ -175,7 +377,52 @@ def test_list_open_workflow_executions(): open_workflow["executionStatus"].should.equal("OPEN") +# ListOpenWorkflowExecutions endpoint +@mock_swf +def test_list_open_workflow_executions_boto3(): + client = setup_swf_environment_boto3() + # One open workflow execution + client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + # One closed workflow execution to make sure it isn't displayed + run_id = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd12345", + workflowType={"name": "test-workflow", "version": "v1.0"}, + )["runId"] + client.terminate_workflow_execution( + domain="test-domain", + workflowId="uid-abcd12345", + details="some details", + reason="a more complete reason", + runId=run_id, + ) + + yesterday = datetime.utcnow() - timedelta(days=1) + oldest_date = unix_time(yesterday) + response = client.list_open_workflow_executions( + domain="test-domain", + startTimeFilter={"oldestDate": oldest_date}, + executionFilter={"workflowId": "test-workflow"}, + ) + execution_infos = response["executionInfos"] + len(execution_infos).should.equal(1) + open_workflow = execution_infos[0] + open_workflow["workflowType"].should.equal( + {"version": "v1.0", "name": "test-workflow"} + ) + open_workflow.should.contain("startTimestamp") + open_workflow["execution"]["workflowId"].should.equal("uid-abcd1234") + open_workflow["execution"].should.contain("runId") + open_workflow["cancelRequested"].should.be(False) + open_workflow["executionStatus"].should.equal("OPEN") + + # ListClosedWorkflowExecutions endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_list_closed_workflow_executions(): conn = setup_swf_environment() @@ -213,7 +460,52 @@ def test_list_closed_workflow_executions(): open_workflow["executionStatus"].should.equal("CLOSED") +# ListClosedWorkflowExecutions endpoint +@mock_swf +def test_list_closed_workflow_executions_boto3(): + client = setup_swf_environment_boto3() + # Leave one workflow execution open to make sure it isn't displayed + client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + # One closed workflow execution + run_id = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd12345", + workflowType={"name": "test-workflow", "version": "v1.0"}, + )["runId"] + client.terminate_workflow_execution( + domain="test-domain", + workflowId="uid-abcd12345", + details="some details", + reason="a more complete reason", + runId=run_id, + ) + + yesterday = datetime.utcnow() - timedelta(days=1) + oldest_date = unix_time(yesterday) + response = client.list_closed_workflow_executions( + domain="test-domain", + startTimeFilter={"oldestDate": oldest_date}, + executionFilter={"workflowId": "test-workflow"}, + ) + execution_infos = response["executionInfos"] + len(execution_infos).should.equal(1) + open_workflow = execution_infos[0] + open_workflow["workflowType"].should.equal( + {"version": "v1.0", "name": "test-workflow"} + ) + open_workflow.should.contain("startTimestamp") + open_workflow["execution"]["workflowId"].should.equal("uid-abcd12345") + open_workflow["execution"].should.contain("runId") + open_workflow["cancelRequested"].should.be(False) + open_workflow["executionStatus"].should.equal("CLOSED") + + # TerminateWorkflowExecution endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_terminate_workflow_execution(): conn = setup_swf_environment() @@ -239,6 +531,36 @@ def test_terminate_workflow_execution(): attrs["cause"].should.equal("OPERATOR_INITIATED") +# TerminateWorkflowExecution endpoint +@mock_swf +def test_terminate_workflow_execution_boto3(): + client = setup_swf_environment_boto3() + run_id = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + )["runId"] + + client.terminate_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + details="some details", + reason="a more complete reason", + runId=run_id, + ) + + resp = client.get_workflow_execution_history( + domain="test-domain", execution={"runId": run_id, "workflowId": "uid-abcd1234"}, + ) + evt = resp["events"][-1] + evt["eventType"].should.equal("WorkflowExecutionTerminated") + attrs = evt["workflowExecutionTerminatedEventAttributes"] + attrs["details"].should.equal("some details") + attrs["reason"].should.equal("a more complete reason") + attrs["cause"].should.equal("OPERATOR_INITIATED") + + +# Has boto3 equivalent @mock_swf_deprecated def test_terminate_workflow_execution_with_wrong_workflow_or_run_id(): conn = setup_swf_environment() @@ -272,3 +594,58 @@ def test_terminate_workflow_execution_with_wrong_workflow_or_run_id(): ).should.throw( SWFResponseError, "WorkflowExecution=[workflowId=uid-abcd1234, runId=" ) + + +@mock_swf +def test_terminate_workflow_execution_with_wrong_workflow_or_run_id_boto3(): + client = setup_swf_environment_boto3() + run_id = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + )["runId"] + + # terminate workflow execution + client.terminate_workflow_execution(domain="test-domain", workflowId="uid-abcd1234") + + # already closed, with run_id + with pytest.raises(ClientError) as ex: + client.terminate_workflow_execution( + domain="test-domain", workflowId="uid-abcd1234", runId=run_id + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown execution: WorkflowExecution=[workflowId=uid-abcd1234, runId={}]".format( + run_id + ) + ) + + # already closed, without run_id + with pytest.raises(ClientError) as ex: + client.terminate_workflow_execution( + domain="test-domain", workflowId="uid-abcd1234" + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown execution, workflowId = uid-abcd1234" + ) + + # wrong workflow id + with pytest.raises(ClientError) as ex: + client.terminate_workflow_execution( + domain="test-domain", workflowId="uid-non-existent" + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown execution, workflowId = uid-non-existent" + ) + + # wrong run_id + with pytest.raises(ClientError) as ex: + client.terminate_workflow_execution( + domain="test-domain", workflowId="uid-abcd1234", runId="foo" + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown execution: WorkflowExecution=[workflowId=uid-abcd1234, runId=foo]" + ) diff --git a/tests/test_swf/responses/test_workflow_types.py b/tests/test_swf/responses/test_workflow_types.py index e1990596b..347f85acb 100644 --- a/tests/test_swf/responses/test_workflow_types.py +++ b/tests/test_swf/responses/test_workflow_types.py @@ -1,6 +1,7 @@ import sure import boto import boto3 +import pytest from moto import mock_swf_deprecated from moto import mock_swf @@ -9,6 +10,7 @@ from botocore.exceptions import ClientError # RegisterWorkflowType endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_register_workflow_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -21,6 +23,26 @@ def test_register_workflow_type(): actype["workflowType"]["version"].should.equal("v1.0") +# RegisterWorkflowType endpoint +@mock_swf +def test_register_workflow_type_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60", + ) + client.register_workflow_type( + domain="test-domain", name="test-workflow", version="v1.0" + ) + + types = client.list_workflow_types( + domain="test-domain", registrationStatus="REGISTERED" + ) + actype = types["typeInfos"][0] + actype["workflowType"]["name"].should.equal("test-workflow") + actype["workflowType"]["version"].should.equal("v1.0") + + +# Has boto3 equivalent @mock_swf_deprecated def test_register_already_existing_workflow_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -32,6 +54,27 @@ def test_register_already_existing_workflow_type(): ).should.throw(SWFResponseError) +@mock_swf +def test_register_already_existing_workflow_type_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60", + ) + client.register_workflow_type( + domain="test-domain", name="test-workflow", version="v1.0" + ) + + with pytest.raises(ClientError) as ex: + client.register_workflow_type( + domain="test-domain", name="test-workflow", version="v1.0" + ) + ex.value.response["Error"]["Code"].should.equal("TypeAlreadyExistsFault") + ex.value.response["Error"]["Message"].should.equal( + "WorkflowType=[name=test-workflow, version=v1.0]" + ) + + +# Has boto3 equivalent @mock_swf_deprecated def test_register_with_wrong_parameter_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -43,6 +86,7 @@ def test_register_with_wrong_parameter_type(): # ListWorkflowTypes endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_list_workflow_types(): conn = boto.connect_swf("the_key", "the_secret") @@ -59,6 +103,34 @@ def test_list_workflow_types(): names.should.equal(["a-test-workflow", "b-test-workflow", "c-test-workflow"]) +# ListWorkflowTypes endpoint +@mock_swf +def test_list_workflow_types_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60", + ) + client.register_workflow_type( + domain="test-domain", name="b-test-workflow", version="v1.0" + ) + client.register_workflow_type( + domain="test-domain", name="a-test-workflow", version="v1.0" + ) + client.register_workflow_type( + domain="test-domain", name="c-test-workflow", version="v1.0" + ) + + all_workflow_types = client.list_workflow_types( + domain="test-domain", registrationStatus="REGISTERED" + ) + names = [ + activity_type["workflowType"]["name"] + for activity_type in all_workflow_types["typeInfos"] + ] + names.should.equal(["a-test-workflow", "b-test-workflow", "c-test-workflow"]) + + +# Has boto3 equivalent @mock_swf_deprecated def test_list_workflow_types_reverse_order(): conn = boto.connect_swf("the_key", "the_secret") @@ -77,7 +149,35 @@ def test_list_workflow_types_reverse_order(): names.should.equal(["c-test-workflow", "b-test-workflow", "a-test-workflow"]) +# ListWorkflowTypes endpoint +@mock_swf +def test_list_workflow_types_reverse_order_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60", + ) + client.register_workflow_type( + domain="test-domain", name="b-test-workflow", version="v1.0" + ) + client.register_workflow_type( + domain="test-domain", name="a-test-workflow", version="v1.0" + ) + client.register_workflow_type( + domain="test-domain", name="c-test-workflow", version="v1.0" + ) + + all_workflow_types = client.list_workflow_types( + domain="test-domain", registrationStatus="REGISTERED", reverseOrder=True + ) + names = [ + activity_type["workflowType"]["name"] + for activity_type in all_workflow_types["typeInfos"] + ] + names.should.equal(["c-test-workflow", "b-test-workflow", "a-test-workflow"]) + + # DeprecateWorkflowType endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_deprecate_workflow_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -91,6 +191,29 @@ def test_deprecate_workflow_type(): actype["workflowType"]["version"].should.equal("v1.0") +# DeprecateWorkflowType endpoint +@mock_swf +def test_deprecate_workflow_type_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60", + ) + client.register_workflow_type( + domain="test-domain", name="test-workflow", version="v1.0" + ) + client.deprecate_workflow_type( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ) + + actypes = client.list_workflow_types( + domain="test-domain", registrationStatus="DEPRECATED" + ) + actype = actypes["typeInfos"][0] + actype["workflowType"]["name"].should.equal("test-workflow") + actype["workflowType"]["version"].should.equal("v1.0") + + +# Has boto3 equivalent @mock_swf_deprecated def test_deprecate_already_deprecated_workflow_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -103,6 +226,31 @@ def test_deprecate_already_deprecated_workflow_type(): ).should.throw(SWFResponseError) +@mock_swf +def test_deprecate_already_deprecated_workflow_type_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60", + ) + client.register_workflow_type( + domain="test-domain", name="test-workflow", version="v1.0" + ) + client.deprecate_workflow_type( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ) + + with pytest.raises(ClientError) as ex: + client.deprecate_workflow_type( + domain="test-domain", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + ex.value.response["Error"]["Code"].should.equal("TypeDeprecatedFault") + ex.value.response["Error"]["Message"].should.equal( + "WorkflowType=[name=test-workflow, version=v1.0]" + ) + + +# Has boto3 equivalent @mock_swf_deprecated def test_deprecate_non_existent_workflow_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -113,6 +261,24 @@ def test_deprecate_non_existent_workflow_type(): ).should.throw(SWFResponseError) +@mock_swf +def test_deprecate_non_existent_workflow_type_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60", + ) + + with pytest.raises(ClientError) as ex: + client.deprecate_workflow_type( + domain="test-domain", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown type: WorkflowType=[name=test-workflow, version=v1.0]" + ) + + # UndeprecateWorkflowType endpoint @mock_swf def test_undeprecate_workflow_type(): @@ -185,6 +351,7 @@ def test_undeprecate_non_existent_workflow_type(): # DescribeWorkflowType endpoint +# Has boto3 equivalent @mock_swf_deprecated def test_describe_workflow_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -242,6 +409,7 @@ def test_describe_workflow_type_full_boto3(): resp["configuration"]["defaultLambdaRole"].should.equal("arn:bar") +# Has boto3 equivalent @mock_swf_deprecated def test_describe_non_existent_workflow_type(): conn = boto.connect_swf("the_key", "the_secret") @@ -250,3 +418,21 @@ def test_describe_non_existent_workflow_type(): conn.describe_workflow_type.when.called_with( "test-domain", "non-existent", "v1.0" ).should.throw(SWFResponseError) + + +@mock_swf +def test_describe_non_existent_workflow_type_boto3(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60", + ) + + with pytest.raises(ClientError) as ex: + client.describe_workflow_type( + domain="test-domain", + workflowType={"name": "non-existent", "version": "v1.0"}, + ) + ex.value.response["Error"]["Code"].should.equal("UnknownResourceFault") + ex.value.response["Error"]["Message"].should.equal( + "Unknown type: WorkflowType=[name=non-existent, version=v1.0]" + ) diff --git a/tests/test_swf/utils.py b/tests/test_swf/utils.py index f30a8d465..c1cc25b65 100644 --- a/tests/test_swf/utils.py +++ b/tests/test_swf/utils.py @@ -1,4 +1,5 @@ import boto +import boto3 from moto.swf.models import ActivityType, Domain, WorkflowType, WorkflowExecution @@ -47,6 +48,17 @@ def _generic_workflow_type_attributes(): ) +def _generic_workflow_type_attributes_boto3(): + return { + "name": "test-workflow", + "version": "v1.0", + "defaultTaskList": {"name": "queue"}, + "defaultChildPolicy": "ABANDON", + "defaultExecutionStartToCloseTimeout": "7200", + "defaultTaskStartToCloseTimeout": "300", + } + + def get_basic_workflow_type(): args, kwargs = _generic_workflow_type_attributes() return WorkflowType(*args, **kwargs) @@ -58,6 +70,12 @@ def mock_basic_workflow_type(domain_name, conn): return conn +def mock_basic_workflow_type_boto3(domain_name, client): + kwargs = _generic_workflow_type_attributes_boto3() + client.register_workflow_type(domain=domain_name, **kwargs) + return client + + # A test WorkflowExecution def make_workflow_execution(**kwargs): domain = get_basic_domain() @@ -93,6 +111,33 @@ def setup_workflow(): return conn +# Setup a complete example workflow and return the connection object +def setup_workflow_boto3(): + client = boto3.client("swf", region_name="us-west-1") + client.register_domain( + name="test-domain", + workflowExecutionRetentionPeriodInDays="60", + description="A test domain", + ) + mock_basic_workflow_type_boto3("test-domain", client) + client.register_activity_type( + domain="test-domain", + name="test-activity", + version="v1.1", + defaultTaskHeartbeatTimeout="600", + defaultTaskScheduleToCloseTimeout="600", + defaultTaskScheduleToStartTimeout="600", + defaultTaskStartToCloseTimeout="600", + ) + wfe = client.start_workflow_execution( + domain="test-domain", + workflowId="uid-abcd1234", + workflowType={"name": "test-workflow", "version": "v1.0"}, + ) + client.run_id = wfe["runId"] + return client + + # A helper for processing the first timeout on a given object def process_first_timeout(obj): _timeout = obj.first_timeout()