47e0ef1a-9bf2-11e1-9279-0100e8cf109a
"""
+
+GET_SEND_STATISTICS = """
+
+
+ {% for statistics in all_statistics %}
+ -
+ {{ statistics["DeliveryAttempts"] }}
+ {{ statistics["Rejects"] }}
+ {{ statistics["Bounces"] }}
+ {{ statistics["Complaints"] }}
+ {{ statistics["Timestamp"] }}
+
+ {% endfor %}
+
+
+ e0abcdfa-c866-11e0-b6d0-273d09173z49
+
+
+"""
+
+CREATE_CONFIGURATION_SET = """
+
+
+ 47e0ef1a-9bf2-11e1-9279-0100e8cf109a
+
+"""
+
+
+CREATE_CONFIGURATION_SET_EVENT_DESTINATION = """
+
+
+ 67e0ef1a-9bf2-11e1-9279-0100e8cf109a
+
+"""
+
+CREATE_TEMPLATE = """
+
+
+ 47e0ef1a-9bf2-11e1-9279-0100e8cf12ba
+
+"""
+
+GET_TEMPLATE = """
+
+
+ {{ template_data["template_name"] }}
+ {{ template_data["subject_part"] }}
+ {{ template_data["html_part"] }}
+ {{ template_data["text_part"] }}
+
+
+
+ 47e0ef1a-9bf2-11e1-9279-0100e8cf12ba
+
+"""
+
+LIST_TEMPLATES = """
+
+
+ {% for template in templates %}
+ -
+ {{ template["template_name"] }}
+ {{ template["Timestamp"] }}
+
+ {% endfor %}
+
+
+
+ 47e0ef1a-9bf2-11e1-9279-0100e8cf12ba
+
+"""
diff --git a/moto/ssm/models.py b/moto/ssm/models.py
index 3ce3b3a22..67216972e 100644
--- a/moto/ssm/models.py
+++ b/moto/ssm/models.py
@@ -514,6 +514,16 @@ class SimpleSystemManagerBackend(BaseBackend):
def get_parameters(self, names, with_decryption):
result = []
+
+ if len(names) > 10:
+ raise ValidationException(
+ "1 validation error detected: "
+ "Value '[{}]' at 'names' failed to satisfy constraint: "
+ "Member must have length less than or equal to 10.".format(
+ ", ".join(names)
+ )
+ )
+
for name in names:
if name in self._parameters:
result.append(self.get_parameter(name, with_decryption))
diff --git a/scripts/implementation_coverage.py b/scripts/implementation_coverage.py
index 4552ec18e..57f978ff9 100755
--- a/scripts/implementation_coverage.py
+++ b/scripts/implementation_coverage.py
@@ -7,18 +7,18 @@ import boto3
script_dir = os.path.dirname(os.path.abspath(__file__))
-alternative_service_names = {'lambda': 'awslambda'}
+alternative_service_names = {'lambda': 'awslambda', 'dynamodb': 'dynamodb2'}
def get_moto_implementation(service_name):
service_name = service_name.replace("-", "") if "-" in service_name else service_name
alt_service_name = alternative_service_names[service_name] if service_name in alternative_service_names else service_name
- if not hasattr(moto, alt_service_name):
- return None
- module = getattr(moto, alt_service_name)
- if module is None:
- return None
- mock = getattr(module, "mock_{}".format(service_name))
+ if hasattr(moto, "mock_{}".format(alt_service_name)):
+ mock = getattr(moto, "mock_{}".format(alt_service_name))
+ elif hasattr(moto, "mock_{}".format(service_name)):
+ mock = getattr(moto, "mock_{}".format(service_name))
+ else:
+ mock = None
if mock is None:
return None
backends = list(mock().backends.values())
@@ -97,12 +97,14 @@ def write_implementation_coverage_to_file(coverage):
file.write("\n")
file.write("## {}\n".format(service_name))
- file.write("{}% implemented\n".format(percentage_implemented))
+ file.write("\n")
+ file.write("{}% implemented
\n\n".format(percentage_implemented))
for op in operations:
if op in implemented:
file.write("- [X] {}\n".format(op))
else:
file.write("- [ ] {}\n".format(op))
+ file.write(" \n")
if __name__ == '__main__':
diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py
index 7495372d2..295cd1c54 100644
--- a/tests/test_apigateway/test_apigateway.py
+++ b/tests/test_apigateway/test_apigateway.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+import json
import boto3
from freezegun import freeze_time
@@ -1230,6 +1231,65 @@ def test_put_integration_response_requires_responseTemplate():
)
+@mock_apigateway
+def test_put_integration_response_with_response_template():
+ client = boto3.client("apigateway", region_name="us-west-2")
+ response = client.create_rest_api(name="my_api", description="this is my api")
+ api_id = response["id"]
+ resources = client.get_resources(restApiId=api_id)
+ root_id = [resource for resource in resources["items"] if resource["path"] == "/"][
+ 0
+ ]["id"]
+
+ client.put_method(
+ restApiId=api_id, resourceId=root_id, httpMethod="GET", authorizationType="NONE"
+ )
+ client.put_method_response(
+ restApiId=api_id, resourceId=root_id, httpMethod="GET", statusCode="200"
+ )
+ client.put_integration(
+ restApiId=api_id,
+ resourceId=root_id,
+ httpMethod="GET",
+ type="HTTP",
+ uri="http://httpbin.org/robots.txt",
+ integrationHttpMethod="POST",
+ )
+
+ with assert_raises(ClientError) as ex:
+ client.put_integration_response(
+ restApiId=api_id, resourceId=root_id, httpMethod="GET", statusCode="200"
+ )
+
+ ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
+ ex.exception.response["Error"]["Message"].should.equal("Invalid request input")
+
+ client.put_integration_response(
+ restApiId=api_id,
+ resourceId=root_id,
+ httpMethod="GET",
+ statusCode="200",
+ selectionPattern="foobar",
+ responseTemplates={"application/json": json.dumps({"data": "test"})},
+ )
+
+ response = client.get_integration_response(
+ restApiId=api_id, resourceId=root_id, httpMethod="GET", statusCode="200"
+ )
+
+ # this is hard to match against, so remove it
+ response["ResponseMetadata"].pop("HTTPHeaders", None)
+ response["ResponseMetadata"].pop("RetryAttempts", None)
+ response.should.equal(
+ {
+ "statusCode": "200",
+ "selectionPattern": "foobar",
+ "ResponseMetadata": {"HTTPStatusCode": 200},
+ "responseTemplates": {"application/json": json.dumps({"data": "test"})},
+ }
+ )
+
+
@mock_apigateway
def test_put_integration_validation():
client = boto3.client("apigateway", region_name="us-west-2")
diff --git a/tests/test_autoscaling/test_autoscaling.py b/tests/test_autoscaling/test_autoscaling.py
index 3a10f20ff..93a8c5a48 100644
--- a/tests/test_autoscaling/test_autoscaling.py
+++ b/tests/test_autoscaling/test_autoscaling.py
@@ -1071,6 +1071,7 @@ def test_autoscaling_describe_policies_boto3():
response["ScalingPolicies"][0]["PolicyName"].should.equal("test_policy_down")
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_detach_one_instance_decrement():
@@ -1096,6 +1097,19 @@ def test_detach_one_instance_decrement():
],
VPCZoneIdentifier=mocked_networking["subnet1"],
)
+
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
instance_to_detach = response["AutoScalingGroups"][0]["Instances"][0]["InstanceId"]
instance_to_keep = response["AutoScalingGroups"][0]["Instances"][1]["InstanceId"]
@@ -1111,6 +1125,9 @@ def test_detach_one_instance_decrement():
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
response["AutoScalingGroups"][0]["Instances"].should.have.length_of(1)
+ instance_to_detach.shouldnt.be.within(
+ [x["InstanceId"] for x in response["AutoScalingGroups"][0]["Instances"]]
+ )
# test to ensure tag has been removed
response = ec2_client.describe_instances(InstanceIds=[instance_to_detach])
@@ -1122,7 +1139,14 @@ def test_detach_one_instance_decrement():
tags = response["Reservations"][0]["Instances"][0]["Tags"]
tags.should.have.length_of(2)
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(1)
+ instance_to_detach.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_detach_one_instance():
@@ -1148,6 +1172,19 @@ def test_detach_one_instance():
],
VPCZoneIdentifier=mocked_networking["subnet1"],
)
+
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
instance_to_detach = response["AutoScalingGroups"][0]["Instances"][0]["InstanceId"]
instance_to_keep = response["AutoScalingGroups"][0]["Instances"][1]["InstanceId"]
@@ -1173,7 +1210,14 @@ def test_detach_one_instance():
tags = response["Reservations"][0]["Instances"][0]["Tags"]
tags.should.have.length_of(2)
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(2)
+ instance_to_detach.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_standby_one_instance_decrement():
@@ -1199,6 +1243,19 @@ def test_standby_one_instance_decrement():
],
VPCZoneIdentifier=mocked_networking["subnet1"],
)
+
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
instance_to_standby = response["AutoScalingGroups"][0]["Instances"][0]["InstanceId"]
instance_to_keep = response["AutoScalingGroups"][0]["Instances"][1]["InstanceId"]
@@ -1226,7 +1283,14 @@ def test_standby_one_instance_decrement():
tags = instance["Tags"]
tags.should.have.length_of(2)
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(1)
+ instance_to_standby.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_standby_one_instance():
@@ -1252,6 +1316,19 @@ def test_standby_one_instance():
],
VPCZoneIdentifier=mocked_networking["subnet1"],
)
+
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
instance_to_standby = response["AutoScalingGroups"][0]["Instances"][0]["InstanceId"]
instance_to_keep = response["AutoScalingGroups"][0]["Instances"][1]["InstanceId"]
@@ -1279,6 +1356,12 @@ def test_standby_one_instance():
tags = instance["Tags"]
tags.should.have.length_of(2)
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(2)
+ instance_to_standby.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+
@mock_elb
@mock_autoscaling
@@ -1338,8 +1421,12 @@ def test_standby_elb_update():
response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(2)
+ instance_to_standby.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_standby_terminate_instance_decrement():
@@ -1366,6 +1453,18 @@ def test_standby_terminate_instance_decrement():
VPCZoneIdentifier=mocked_networking["subnet1"],
)
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
instance_to_standby_terminate = response["AutoScalingGroups"][0]["Instances"][0][
"InstanceId"
@@ -1409,7 +1508,14 @@ def test_standby_terminate_instance_decrement():
"terminated"
)
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(1)
+ instance_to_standby_terminate.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_standby_terminate_instance_no_decrement():
@@ -1436,6 +1542,18 @@ def test_standby_terminate_instance_no_decrement():
VPCZoneIdentifier=mocked_networking["subnet1"],
)
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
instance_to_standby_terminate = response["AutoScalingGroups"][0]["Instances"][0][
"InstanceId"
@@ -1479,7 +1597,14 @@ def test_standby_terminate_instance_no_decrement():
"terminated"
)
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(2)
+ instance_to_standby_terminate.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_standby_detach_instance_decrement():
@@ -1506,6 +1631,18 @@ def test_standby_detach_instance_decrement():
VPCZoneIdentifier=mocked_networking["subnet1"],
)
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
instance_to_standby_detach = response["AutoScalingGroups"][0]["Instances"][0][
"InstanceId"
@@ -1547,7 +1684,14 @@ def test_standby_detach_instance_decrement():
response = ec2_client.describe_instances(InstanceIds=[instance_to_standby_detach])
response["Reservations"][0]["Instances"][0]["State"]["Name"].should.equal("running")
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(1)
+ instance_to_standby_detach.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_standby_detach_instance_no_decrement():
@@ -1574,6 +1718,18 @@ def test_standby_detach_instance_no_decrement():
VPCZoneIdentifier=mocked_networking["subnet1"],
)
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
instance_to_standby_detach = response["AutoScalingGroups"][0]["Instances"][0][
"InstanceId"
@@ -1615,7 +1771,14 @@ def test_standby_detach_instance_no_decrement():
response = ec2_client.describe_instances(InstanceIds=[instance_to_standby_detach])
response["Reservations"][0]["Instances"][0]["State"]["Name"].should.equal("running")
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(2)
+ instance_to_standby_detach.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_standby_exit_standby():
@@ -1642,6 +1805,18 @@ def test_standby_exit_standby():
VPCZoneIdentifier=mocked_networking["subnet1"],
)
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
instance_to_standby_exit_standby = response["AutoScalingGroups"][0]["Instances"][0][
"InstanceId"
@@ -1683,7 +1858,14 @@ def test_standby_exit_standby():
)
response["Reservations"][0]["Instances"][0]["State"]["Name"].should.equal("running")
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(3)
+ instance_to_standby_exit_standby.should.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
+
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_attach_one_instance():
@@ -1711,6 +1893,18 @@ def test_attach_one_instance():
NewInstancesProtectedFromScaleIn=True,
)
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
ec2 = boto3.resource("ec2", "us-east-1")
instances_to_add = [
x.id for x in ec2.create_instances(ImageId="", MinCount=1, MaxCount=1)
@@ -1727,6 +1921,9 @@ def test_attach_one_instance():
for instance in instances:
instance["ProtectedFromScaleIn"].should.equal(True)
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(3)
+
@mock_autoscaling
@mock_ec2
@@ -1948,6 +2145,7 @@ def test_terminate_instance_via_ec2_in_autoscaling_group():
replaced_instance_id.should_not.equal(original_instance_id)
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_terminate_instance_in_auto_scaling_group_decrement():
@@ -1966,6 +2164,18 @@ def test_terminate_instance_in_auto_scaling_group_decrement():
NewInstancesProtectedFromScaleIn=False,
)
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
original_instance_id = next(
instance["InstanceId"]
@@ -1979,7 +2189,11 @@ def test_terminate_instance_in_auto_scaling_group_decrement():
response["AutoScalingGroups"][0]["Instances"].should.equal([])
response["AutoScalingGroups"][0]["DesiredCapacity"].should.equal(0)
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(0)
+
+@mock_elb
@mock_autoscaling
@mock_ec2
def test_terminate_instance_in_auto_scaling_group_no_decrement():
@@ -1998,6 +2212,18 @@ def test_terminate_instance_in_auto_scaling_group_no_decrement():
NewInstancesProtectedFromScaleIn=False,
)
+ elb_client = boto3.client("elb", region_name="us-east-1")
+ elb_client.create_load_balancer(
+ LoadBalancerName="my-lb",
+ Listeners=[{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}],
+ AvailabilityZones=["us-east-1a", "us-east-1b"],
+ )
+
+ response = client.attach_load_balancers(
+ AutoScalingGroupName="test_asg", LoadBalancerNames=["my-lb"]
+ )
+ response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
original_instance_id = next(
instance["InstanceId"]
@@ -2014,3 +2240,9 @@ def test_terminate_instance_in_auto_scaling_group_no_decrement():
)
replaced_instance_id.should_not.equal(original_instance_id)
response["AutoScalingGroups"][0]["DesiredCapacity"].should.equal(1)
+
+ response = elb_client.describe_load_balancers(LoadBalancerNames=["my-lb"])
+ list(response["LoadBalancerDescriptions"][0]["Instances"]).should.have.length_of(1)
+ original_instance_id.shouldnt.be.within(
+ [x["InstanceId"] for x in response["LoadBalancerDescriptions"][0]["Instances"]]
+ )
diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py
index 470c5f8ff..50fd4fd6c 100644
--- a/tests/test_dynamodb2/test_dynamodb.py
+++ b/tests/test_dynamodb2/test_dynamodb.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals, print_function
+from datetime import datetime
from decimal import Decimal
import boto
@@ -2049,6 +2050,141 @@ def test_set_ttl():
resp["TimeToLiveDescription"]["TimeToLiveStatus"].should.equal("DISABLED")
+@mock_dynamodb2
+def test_describe_continuous_backups():
+ # given
+ client = boto3.client("dynamodb", region_name="us-east-1")
+ table_name = client.create_table(
+ TableName="test",
+ AttributeDefinitions=[
+ {"AttributeName": "client", "AttributeType": "S"},
+ {"AttributeName": "app", "AttributeType": "S"},
+ ],
+ KeySchema=[
+ {"AttributeName": "client", "KeyType": "HASH"},
+ {"AttributeName": "app", "KeyType": "RANGE"},
+ ],
+ BillingMode="PAY_PER_REQUEST",
+ )["TableDescription"]["TableName"]
+
+ # when
+ response = client.describe_continuous_backups(TableName=table_name)
+
+ # then
+ response["ContinuousBackupsDescription"].should.equal(
+ {
+ "ContinuousBackupsStatus": "ENABLED",
+ "PointInTimeRecoveryDescription": {"PointInTimeRecoveryStatus": "DISABLED"},
+ }
+ )
+
+
+@mock_dynamodb2
+def test_describe_continuous_backups_errors():
+ # given
+ client = boto3.client("dynamodb", region_name="us-east-1")
+
+ # when
+ with assert_raises(Exception) as e:
+ client.describe_continuous_backups(TableName="not-existing-table")
+
+ # then
+ ex = e.exception
+ ex.operation_name.should.equal("DescribeContinuousBackups")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("TableNotFoundException")
+ ex.response["Error"]["Message"].should.equal("Table not found: not-existing-table")
+
+
+@mock_dynamodb2
+def test_update_continuous_backups():
+ # given
+ client = boto3.client("dynamodb", region_name="us-east-1")
+ table_name = client.create_table(
+ TableName="test",
+ AttributeDefinitions=[
+ {"AttributeName": "client", "AttributeType": "S"},
+ {"AttributeName": "app", "AttributeType": "S"},
+ ],
+ KeySchema=[
+ {"AttributeName": "client", "KeyType": "HASH"},
+ {"AttributeName": "app", "KeyType": "RANGE"},
+ ],
+ BillingMode="PAY_PER_REQUEST",
+ )["TableDescription"]["TableName"]
+
+ # when
+ response = client.update_continuous_backups(
+ TableName=table_name,
+ PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": True},
+ )
+
+ # then
+ response["ContinuousBackupsDescription"]["ContinuousBackupsStatus"].should.equal(
+ "ENABLED"
+ )
+ point_in_time = response["ContinuousBackupsDescription"][
+ "PointInTimeRecoveryDescription"
+ ]
+ earliest_datetime = point_in_time["EarliestRestorableDateTime"]
+ earliest_datetime.should.be.a(datetime)
+ latest_datetime = point_in_time["LatestRestorableDateTime"]
+ latest_datetime.should.be.a(datetime)
+ point_in_time["PointInTimeRecoveryStatus"].should.equal("ENABLED")
+
+ # when
+ # a second update should not change anything
+ response = client.update_continuous_backups(
+ TableName=table_name,
+ PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": True},
+ )
+
+ # then
+ response["ContinuousBackupsDescription"]["ContinuousBackupsStatus"].should.equal(
+ "ENABLED"
+ )
+ point_in_time = response["ContinuousBackupsDescription"][
+ "PointInTimeRecoveryDescription"
+ ]
+ point_in_time["EarliestRestorableDateTime"].should.equal(earliest_datetime)
+ point_in_time["LatestRestorableDateTime"].should.equal(latest_datetime)
+ point_in_time["PointInTimeRecoveryStatus"].should.equal("ENABLED")
+
+ # when
+ response = client.update_continuous_backups(
+ TableName=table_name,
+ PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": False},
+ )
+
+ # then
+ response["ContinuousBackupsDescription"].should.equal(
+ {
+ "ContinuousBackupsStatus": "ENABLED",
+ "PointInTimeRecoveryDescription": {"PointInTimeRecoveryStatus": "DISABLED"},
+ }
+ )
+
+
+@mock_dynamodb2
+def test_update_continuous_backups_errors():
+ # given
+ client = boto3.client("dynamodb", region_name="us-east-1")
+
+ # when
+ with assert_raises(Exception) as e:
+ client.update_continuous_backups(
+ TableName="not-existing-table",
+ PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": True},
+ )
+
+ # then
+ ex = e.exception
+ ex.operation_name.should.equal("UpdateContinuousBackups")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("TableNotFoundException")
+ ex.response["Error"]["Message"].should.equal("Table not found: not-existing-table")
+
+
# https://github.com/spulec/moto/issues/1043
@mock_dynamodb2
def test_query_missing_expr_names():
@@ -4298,13 +4434,8 @@ def test_transact_write_items_put_conditional_expressions():
]
)
# Assert the exception is correct
- ex.exception.response["Error"]["Code"].should.equal(
- "ConditionalCheckFailedException"
- )
+ ex.exception.response["Error"]["Code"].should.equal("TransactionCanceledException")
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
- ex.exception.response["Error"]["Message"].should.equal(
- "A condition specified in the operation could not be evaluated."
- )
# Assert all are present
items = dynamodb.scan(TableName="test-table")["Items"]
items.should.have.length_of(1)
@@ -4393,13 +4524,8 @@ def test_transact_write_items_conditioncheck_fails():
]
)
# Assert the exception is correct
- ex.exception.response["Error"]["Code"].should.equal(
- "ConditionalCheckFailedException"
- )
+ ex.exception.response["Error"]["Code"].should.equal("TransactionCanceledException")
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
- ex.exception.response["Error"]["Message"].should.equal(
- "A condition specified in the operation could not be evaluated."
- )
# Assert the original email address is still present
items = dynamodb.scan(TableName="test-table")["Items"]
@@ -4495,13 +4621,8 @@ def test_transact_write_items_delete_with_failed_condition_expression():
]
)
# Assert the exception is correct
- ex.exception.response["Error"]["Code"].should.equal(
- "ConditionalCheckFailedException"
- )
+ ex.exception.response["Error"]["Code"].should.equal("TransactionCanceledException")
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
- ex.exception.response["Error"]["Message"].should.equal(
- "A condition specified in the operation could not be evaluated."
- )
# Assert the original item is still present
items = dynamodb.scan(TableName="test-table")["Items"]
items.should.have.length_of(1)
@@ -4573,13 +4694,8 @@ def test_transact_write_items_update_with_failed_condition_expression():
]
)
# Assert the exception is correct
- ex.exception.response["Error"]["Code"].should.equal(
- "ConditionalCheckFailedException"
- )
+ ex.exception.response["Error"]["Code"].should.equal("TransactionCanceledException")
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
- ex.exception.response["Error"]["Message"].should.equal(
- "A condition specified in the operation could not be evaluated."
- )
# Assert the original item is still present
items = dynamodb.scan(TableName="test-table")["Items"]
items.should.have.length_of(1)
@@ -5029,3 +5145,126 @@ def test_update_item_atomic_counter_return_values():
"v" in response["Attributes"]
), "v has been updated, and should be returned here"
response["Attributes"]["v"]["N"].should.equal("8")
+
+
+@mock_dynamodb2
+def test_update_item_atomic_counter_from_zero():
+ table = "table_t"
+ ddb_mock = boto3.client("dynamodb", region_name="eu-west-1")
+ ddb_mock.create_table(
+ TableName=table,
+ KeySchema=[{"AttributeName": "t_id", "KeyType": "HASH"}],
+ AttributeDefinitions=[{"AttributeName": "t_id", "AttributeType": "S"}],
+ BillingMode="PAY_PER_REQUEST",
+ )
+
+ key = {"t_id": {"S": "item1"}}
+
+ ddb_mock.put_item(
+ TableName=table, Item=key,
+ )
+
+ ddb_mock.update_item(
+ TableName=table,
+ Key=key,
+ UpdateExpression="add n_i :inc1, n_f :inc2",
+ ExpressionAttributeValues={":inc1": {"N": "1.2"}, ":inc2": {"N": "-0.5"}},
+ )
+ updated_item = ddb_mock.get_item(TableName=table, Key=key)["Item"]
+ assert updated_item["n_i"]["N"] == "1.2"
+ assert updated_item["n_f"]["N"] == "-0.5"
+
+
+@mock_dynamodb2
+def test_update_item_add_to_non_existent_set():
+ table = "table_t"
+ ddb_mock = boto3.client("dynamodb", region_name="eu-west-1")
+ ddb_mock.create_table(
+ TableName=table,
+ KeySchema=[{"AttributeName": "t_id", "KeyType": "HASH"}],
+ AttributeDefinitions=[{"AttributeName": "t_id", "AttributeType": "S"}],
+ BillingMode="PAY_PER_REQUEST",
+ )
+ key = {"t_id": {"S": "item1"}}
+ ddb_mock.put_item(
+ TableName=table, Item=key,
+ )
+
+ ddb_mock.update_item(
+ TableName=table,
+ Key=key,
+ UpdateExpression="add s_i :s1",
+ ExpressionAttributeValues={":s1": {"SS": ["hello"]}},
+ )
+ updated_item = ddb_mock.get_item(TableName=table, Key=key)["Item"]
+ assert updated_item["s_i"]["SS"] == ["hello"]
+
+
+@mock_dynamodb2
+def test_update_item_add_to_non_existent_number_set():
+ table = "table_t"
+ ddb_mock = boto3.client("dynamodb", region_name="eu-west-1")
+ ddb_mock.create_table(
+ TableName=table,
+ KeySchema=[{"AttributeName": "t_id", "KeyType": "HASH"}],
+ AttributeDefinitions=[{"AttributeName": "t_id", "AttributeType": "S"}],
+ BillingMode="PAY_PER_REQUEST",
+ )
+ key = {"t_id": {"S": "item1"}}
+ ddb_mock.put_item(
+ TableName=table, Item=key,
+ )
+
+ ddb_mock.update_item(
+ TableName=table,
+ Key=key,
+ UpdateExpression="add s_i :s1",
+ ExpressionAttributeValues={":s1": {"NS": ["3"]}},
+ )
+ updated_item = ddb_mock.get_item(TableName=table, Key=key)["Item"]
+ assert updated_item["s_i"]["NS"] == ["3"]
+
+
+@mock_dynamodb2
+def test_transact_write_items_fails_with_transaction_canceled_exception():
+ table_schema = {
+ "KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
+ "AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"},],
+ }
+ dynamodb = boto3.client("dynamodb", region_name="us-east-1")
+ dynamodb.create_table(
+ TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
+ )
+ # Insert one item
+ dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}})
+ # Update two items, the one that exists and another that doesn't
+ with assert_raises(ClientError) as ex:
+ dynamodb.transact_write_items(
+ TransactItems=[
+ {
+ "Update": {
+ "Key": {"id": {"S": "foo"}},
+ "TableName": "test-table",
+ "UpdateExpression": "SET #k = :v",
+ "ConditionExpression": "attribute_exists(id)",
+ "ExpressionAttributeNames": {"#k": "key"},
+ "ExpressionAttributeValues": {":v": {"S": "value"}},
+ }
+ },
+ {
+ "Update": {
+ "Key": {"id": {"S": "doesnotexist"}},
+ "TableName": "test-table",
+ "UpdateExpression": "SET #e = :v",
+ "ConditionExpression": "attribute_exists(id)",
+ "ExpressionAttributeNames": {"#e": "key"},
+ "ExpressionAttributeValues": {":v": {"S": "value"}},
+ }
+ },
+ ]
+ )
+ ex.exception.response["Error"]["Code"].should.equal("TransactionCanceledException")
+ ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.exception.response["Error"]["Message"].should.equal(
+ "Transaction cancelled, please refer cancellation reasons for specific reasons [None, ConditionalCheckFailed]"
+ )
diff --git a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py
index 6fba713ec..33f65d5ec 100644
--- a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py
+++ b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py
@@ -1307,16 +1307,16 @@ def test_update_item_add_with_expression():
ExpressionAttributeValues={":v": {"item4"}},
)
current_item["str_set"] = current_item["str_set"].union({"item4"})
- dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
+ assert dict(table.get_item(Key=item_key)["Item"]) == current_item
# Update item to add a string value to a non-existing set
- # Should throw: 'The provided key element does not match the schema'
- assert_failure_due_to_key_not_in_schema(
- table.update_item,
+ table.update_item(
Key=item_key,
UpdateExpression="ADD non_existing_str_set :v",
ExpressionAttributeValues={":v": {"item4"}},
)
+ current_item["non_existing_str_set"] = {"item4"}
+ assert dict(table.get_item(Key=item_key)["Item"]) == current_item
# Update item to add a num value to a num set
table.update_item(
@@ -1325,7 +1325,7 @@ def test_update_item_add_with_expression():
ExpressionAttributeValues={":v": {6}},
)
current_item["num_set"] = current_item["num_set"].union({6})
- dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
+ assert dict(table.get_item(Key=item_key)["Item"]) == current_item
# Update item to add a value to a number value
table.update_item(
@@ -1334,7 +1334,7 @@ def test_update_item_add_with_expression():
ExpressionAttributeValues={":v": 20},
)
current_item["num_val"] = current_item["num_val"] + 20
- dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
+ assert dict(table.get_item(Key=item_key)["Item"]) == current_item
# Attempt to add a number value to a string set, should raise Client Error
table.update_item.when.called_with(
@@ -1342,7 +1342,7 @@ def test_update_item_add_with_expression():
UpdateExpression="ADD str_set :v",
ExpressionAttributeValues={":v": 20},
).should.have.raised(ClientError)
- dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
+ assert dict(table.get_item(Key=item_key)["Item"]) == current_item
# Attempt to add a number set to the string set, should raise a ClientError
table.update_item.when.called_with(
@@ -1350,7 +1350,7 @@ def test_update_item_add_with_expression():
UpdateExpression="ADD str_set :v",
ExpressionAttributeValues={":v": {20}},
).should.have.raised(ClientError)
- dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
+ assert dict(table.get_item(Key=item_key)["Item"]) == current_item
# Attempt to update with a bad expression
table.update_item.when.called_with(
@@ -1388,17 +1388,18 @@ def test_update_item_add_with_nested_sets():
current_item["nested"]["str_set"] = current_item["nested"]["str_set"].union(
{"item4"}
)
- dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
+ assert dict(table.get_item(Key=item_key)["Item"]) == current_item
# Update item to add a string value to a non-existing set
# Should raise
- assert_failure_due_to_key_not_in_schema(
- table.update_item,
+ table.update_item(
Key=item_key,
UpdateExpression="ADD #ns.#ne :v",
ExpressionAttributeNames={"#ns": "nested", "#ne": "non_existing_str_set"},
ExpressionAttributeValues={":v": {"new_item"}},
)
+ current_item["nested"]["non_existing_str_set"] = {"new_item"}
+ assert dict(table.get_item(Key=item_key)["Item"]) == current_item
@mock_dynamodb2
diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py
index 0509e1a45..d53bd14aa 100644
--- a/tests/test_ec2/test_instances.py
+++ b/tests/test_ec2/test_instances.py
@@ -1126,6 +1126,111 @@ def test_run_instance_with_keypair():
instance.key_name.should.equal("keypair_name")
+@mock_ec2
+def test_run_instance_with_block_device_mappings():
+ ec2_client = boto3.client("ec2", region_name="us-east-1")
+
+ kwargs = {
+ "MinCount": 1,
+ "MaxCount": 1,
+ "ImageId": "ami-d3adb33f",
+ "KeyName": "the_key",
+ "InstanceType": "t1.micro",
+ "BlockDeviceMappings": [{"DeviceName": "/dev/sda2", "Ebs": {"VolumeSize": 50}}],
+ }
+
+ ec2_client.run_instances(**kwargs)
+
+ instances = ec2_client.describe_instances()
+ volume = instances["Reservations"][0]["Instances"][0]["BlockDeviceMappings"][0][
+ "Ebs"
+ ]
+
+ volumes = ec2_client.describe_volumes(VolumeIds=[volume["VolumeId"]])
+ volumes["Volumes"][0]["Size"].should.equal(50)
+
+
+@mock_ec2
+def test_run_instance_with_block_device_mappings_missing_ebs():
+ ec2_client = boto3.client("ec2", region_name="us-east-1")
+
+ kwargs = {
+ "MinCount": 1,
+ "MaxCount": 1,
+ "ImageId": "ami-d3adb33f",
+ "KeyName": "the_key",
+ "InstanceType": "t1.micro",
+ "BlockDeviceMappings": [{"DeviceName": "/dev/sda2"}],
+ }
+ with assert_raises(ClientError) as ex:
+ ec2_client.run_instances(**kwargs)
+
+ ex.exception.response["Error"]["Code"].should.equal("MissingParameter")
+ ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.exception.response["Error"]["Message"].should.equal(
+ "The request must contain the parameter ebs"
+ )
+
+
+@mock_ec2
+def test_run_instance_with_block_device_mappings_missing_size():
+ ec2_client = boto3.client("ec2", region_name="us-east-1")
+
+ kwargs = {
+ "MinCount": 1,
+ "MaxCount": 1,
+ "ImageId": "ami-d3adb33f",
+ "KeyName": "the_key",
+ "InstanceType": "t1.micro",
+ "BlockDeviceMappings": [
+ {"DeviceName": "/dev/sda2", "Ebs": {"VolumeType": "standard"}}
+ ],
+ }
+ with assert_raises(ClientError) as ex:
+ ec2_client.run_instances(**kwargs)
+
+ ex.exception.response["Error"]["Code"].should.equal("MissingParameter")
+ ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.exception.response["Error"]["Message"].should.equal(
+ "The request must contain the parameter size or snapshotId"
+ )
+
+
+@mock_ec2
+def test_run_instance_with_block_device_mappings_from_snapshot():
+ ec2_client = boto3.client("ec2", region_name="us-east-1")
+ ec2_resource = boto3.resource("ec2", region_name="us-east-1")
+ volume_details = {
+ "AvailabilityZone": "1a",
+ "Size": 30,
+ }
+
+ volume = ec2_resource.create_volume(**volume_details)
+ snapshot = volume.create_snapshot()
+ kwargs = {
+ "MinCount": 1,
+ "MaxCount": 1,
+ "ImageId": "ami-d3adb33f",
+ "KeyName": "the_key",
+ "InstanceType": "t1.micro",
+ "BlockDeviceMappings": [
+ {"DeviceName": "/dev/sda2", "Ebs": {"SnapshotId": snapshot.snapshot_id}}
+ ],
+ }
+
+ ec2_client.run_instances(**kwargs)
+
+ instances = ec2_client.describe_instances()
+ volume = instances["Reservations"][0]["Instances"][0]["BlockDeviceMappings"][0][
+ "Ebs"
+ ]
+
+ volumes = ec2_client.describe_volumes(VolumeIds=[volume["VolumeId"]])
+
+ volumes["Volumes"][0]["Size"].should.equal(30)
+ volumes["Volumes"][0]["SnapshotId"].should.equal(snapshot.snapshot_id)
+
+
@mock_ec2_deprecated
def test_describe_instance_status_no_instances():
conn = boto.connect_ec2("the_key", "the_secret")
diff --git a/tests/test_ec2_instance_connect/test_ec2_instance_connect_boto3.py b/tests/test_ec2instanceconnect/test_ec2instanceconnect_boto3.py
similarity index 92%
rename from tests/test_ec2_instance_connect/test_ec2_instance_connect_boto3.py
rename to tests/test_ec2instanceconnect/test_ec2instanceconnect_boto3.py
index eb685d80a..3f676af96 100644
--- a/tests/test_ec2_instance_connect/test_ec2_instance_connect_boto3.py
+++ b/tests/test_ec2instanceconnect/test_ec2instanceconnect_boto3.py
@@ -1,6 +1,6 @@
import boto3
-from moto import mock_ec2_instance_connect
+from moto import mock_ec2instanceconnect
pubkey = """ssh-rsa
AAAAB3NzaC1yc2EAAAADAQABAAABAQDV5+voluw2zmzqpqCAqtsyoP01TQ8Ydx1eS1yD6wUsHcPqMIqpo57YxiC8XPwrdeKQ6GG6MC3bHsgXoPypGP0LyixbiuLTU31DnnqorcHt4bWs6rQa7dK2pCCflz2fhYRt5ZjqSNsAKivIbqkH66JozN0SySIka3kEV79GdB0BicioKeEJlCwM9vvxafyzjWf/z8E0lh4ni3vkLpIVJ0t5l+Qd9QMJrT6Is0SCQPVagTYZoi8+fWDoGsBa8vyRwDjEzBl28ZplKh9tSyDkRIYszWTpmK8qHiqjLYZBfAxXjGJbEYL1iig4ZxvbYzKEiKSBi1ZMW9iWjHfZDZuxXAmB
@@ -8,7 +8,7 @@ example
"""
-@mock_ec2_instance_connect
+@mock_ec2instanceconnect
def test_send_ssh_public_key():
client = boto3.client("ec2-instance-connect", region_name="us-east-1")
fake_request_id = "example-2a47-4c91-9700-e37e85162cb6"
diff --git a/tests/test_iot/test_iot.py b/tests/test_iot/test_iot.py
index 58a820fee..c3ee4c96d 100644
--- a/tests/test_iot/test_iot.py
+++ b/tests/test_iot/test_iot.py
@@ -9,6 +9,38 @@ from botocore.exceptions import ClientError
from nose.tools import assert_raises
+def generate_thing_group_tree(iot_client, tree_dict, _parent=None):
+ """
+ Generates a thing group tree given the input tree structure.
+ :param iot_client: the iot client for boto3
+ :param tree_dict: dictionary with the key being the group_name, and the value being a sub tree.
+ tree_dict = {
+ "group_name_1a":{
+ "group_name_2a":{
+ "group_name_3a":{} or None
+ },
+ },
+ "group_name_1b":{}
+ }
+ :return: a dictionary of created groups, keyed by group name
+ """
+ if tree_dict is None:
+ tree_dict = {}
+ created_dict = {}
+ for group_name in tree_dict.keys():
+ params = {"thingGroupName": group_name}
+ if _parent:
+ params["parentGroupName"] = _parent
+ created_group = iot_client.create_thing_group(**params)
+ created_dict[group_name] = created_group
+ subtree_dict = generate_thing_group_tree(
+ iot_client=iot_client, tree_dict=tree_dict[group_name], _parent=group_name
+ )
+ created_dict.update(created_dict)
+ created_dict.update(subtree_dict)
+ return created_dict
+
+
@mock_iot
def test_attach_policy():
client = boto3.client("iot", region_name="ap-northeast-1")
@@ -756,25 +788,143 @@ def test_delete_principal_thing():
client.delete_certificate(certificateId=cert_id)
+class TestListThingGroup:
+ group_name_1a = "my-group-name-1a"
+ group_name_1b = "my-group-name-1b"
+ group_name_2a = "my-group-name-2a"
+ group_name_2b = "my-group-name-2b"
+ group_name_3a = "my-group-name-3a"
+ group_name_3b = "my-group-name-3b"
+ group_name_3c = "my-group-name-3c"
+ group_name_3d = "my-group-name-3d"
+ tree_dict = {
+ group_name_1a: {
+ group_name_2a: {group_name_3a: {}, group_name_3b: {}},
+ group_name_2b: {group_name_3c: {}, group_name_3d: {}},
+ },
+ group_name_1b: {},
+ }
+
+ @mock_iot
+ def test_should_list_all_groups(self):
+ # setup
+ client = boto3.client("iot", region_name="ap-northeast-1")
+ group_catalog = generate_thing_group_tree(client, self.tree_dict)
+ # test
+ resp = client.list_thing_groups()
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(8)
+
+ @mock_iot
+ def test_should_list_all_groups_non_recursively(self):
+ # setup
+ client = boto3.client("iot", region_name="ap-northeast-1")
+ group_catalog = generate_thing_group_tree(client, self.tree_dict)
+ # test
+ resp = client.list_thing_groups(recursive=False)
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(2)
+
+ @mock_iot
+ def test_should_list_all_groups_filtered_by_parent(self):
+ # setup
+ client = boto3.client("iot", region_name="ap-northeast-1")
+ group_catalog = generate_thing_group_tree(client, self.tree_dict)
+ # test
+ resp = client.list_thing_groups(parentGroup=self.group_name_1a)
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(6)
+ resp = client.list_thing_groups(parentGroup=self.group_name_2a)
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(2)
+ resp = client.list_thing_groups(parentGroup=self.group_name_1b)
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(0)
+ with assert_raises(ClientError) as e:
+ client.list_thing_groups(parentGroup="inexistant-group-name")
+ e.exception.response["Error"]["Code"].should.equal(
+ "ResourceNotFoundException"
+ )
+
+ @mock_iot
+ def test_should_list_all_groups_filtered_by_parent_non_recursively(self):
+ # setup
+ client = boto3.client("iot", region_name="ap-northeast-1")
+ group_catalog = generate_thing_group_tree(client, self.tree_dict)
+ # test
+ resp = client.list_thing_groups(parentGroup=self.group_name_1a, recursive=False)
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(2)
+ resp = client.list_thing_groups(parentGroup=self.group_name_2a, recursive=False)
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(2)
+
+ @mock_iot
+ def test_should_list_all_groups_filtered_by_name_prefix(self):
+ # setup
+ client = boto3.client("iot", region_name="ap-northeast-1")
+ group_catalog = generate_thing_group_tree(client, self.tree_dict)
+ # test
+ resp = client.list_thing_groups(namePrefixFilter="my-group-name-1")
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(2)
+ resp = client.list_thing_groups(namePrefixFilter="my-group-name-3")
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(4)
+ resp = client.list_thing_groups(namePrefixFilter="prefix-which-doesn-not-match")
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(0)
+
+ @mock_iot
+ def test_should_list_all_groups_filtered_by_name_prefix_non_recursively(self):
+ # setup
+ client = boto3.client("iot", region_name="ap-northeast-1")
+ group_catalog = generate_thing_group_tree(client, self.tree_dict)
+ # test
+ resp = client.list_thing_groups(
+ namePrefixFilter="my-group-name-1", recursive=False
+ )
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(2)
+ resp = client.list_thing_groups(
+ namePrefixFilter="my-group-name-3", recursive=False
+ )
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(0)
+
+ @mock_iot
+ def test_should_list_all_groups_filtered_by_name_prefix_and_parent(self):
+ # setup
+ client = boto3.client("iot", region_name="ap-northeast-1")
+ group_catalog = generate_thing_group_tree(client, self.tree_dict)
+ # test
+ resp = client.list_thing_groups(
+ namePrefixFilter="my-group-name-2", parentGroup=self.group_name_1a
+ )
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(2)
+ resp = client.list_thing_groups(
+ namePrefixFilter="my-group-name-3", parentGroup=self.group_name_1a
+ )
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(4)
+ resp = client.list_thing_groups(
+ namePrefixFilter="prefix-which-doesn-not-match",
+ parentGroup=self.group_name_1a,
+ )
+ resp.should.have.key("thingGroups")
+ resp["thingGroups"].should.have.length_of(0)
+
+
@mock_iot
def test_delete_thing_group():
client = boto3.client("iot", region_name="ap-northeast-1")
group_name_1a = "my-group-name-1a"
group_name_2a = "my-group-name-2a"
- # --1a
- # |--2a
-
- # create thing groups tree
- # 1
- thing_group1a = client.create_thing_group(thingGroupName=group_name_1a)
- thing_group1a.should.have.key("thingGroupName").which.should.equal(group_name_1a)
- thing_group1a.should.have.key("thingGroupArn")
- # 2
- thing_group2a = client.create_thing_group(
- thingGroupName=group_name_2a, parentGroupName=group_name_1a
- )
- thing_group2a.should.have.key("thingGroupName").which.should.equal(group_name_2a)
- thing_group2a.should.have.key("thingGroupArn")
+ tree_dict = {
+ group_name_1a: {group_name_2a: {},},
+ }
+ group_catalog = generate_thing_group_tree(client, tree_dict)
# delete group with child
try:
@@ -809,56 +959,14 @@ def test_describe_thing_group_metadata_hierarchy():
group_name_3c = "my-group-name-3c"
group_name_3d = "my-group-name-3d"
- # --1a
- # |--2a
- # | |--3a
- # | |--3b
- # |
- # |--2b
- # |--3c
- # |--3d
- # --1b
-
- # create thing groups tree
- # 1
- thing_group1a = client.create_thing_group(thingGroupName=group_name_1a)
- thing_group1a.should.have.key("thingGroupName").which.should.equal(group_name_1a)
- thing_group1a.should.have.key("thingGroupArn")
- thing_group1b = client.create_thing_group(thingGroupName=group_name_1b)
- thing_group1b.should.have.key("thingGroupName").which.should.equal(group_name_1b)
- thing_group1b.should.have.key("thingGroupArn")
- # 2
- thing_group2a = client.create_thing_group(
- thingGroupName=group_name_2a, parentGroupName=group_name_1a
- )
- thing_group2a.should.have.key("thingGroupName").which.should.equal(group_name_2a)
- thing_group2a.should.have.key("thingGroupArn")
- thing_group2b = client.create_thing_group(
- thingGroupName=group_name_2b, parentGroupName=group_name_1a
- )
- thing_group2b.should.have.key("thingGroupName").which.should.equal(group_name_2b)
- thing_group2b.should.have.key("thingGroupArn")
- # 3
- thing_group3a = client.create_thing_group(
- thingGroupName=group_name_3a, parentGroupName=group_name_2a
- )
- thing_group3a.should.have.key("thingGroupName").which.should.equal(group_name_3a)
- thing_group3a.should.have.key("thingGroupArn")
- thing_group3b = client.create_thing_group(
- thingGroupName=group_name_3b, parentGroupName=group_name_2a
- )
- thing_group3b.should.have.key("thingGroupName").which.should.equal(group_name_3b)
- thing_group3b.should.have.key("thingGroupArn")
- thing_group3c = client.create_thing_group(
- thingGroupName=group_name_3c, parentGroupName=group_name_2b
- )
- thing_group3c.should.have.key("thingGroupName").which.should.equal(group_name_3c)
- thing_group3c.should.have.key("thingGroupArn")
- thing_group3d = client.create_thing_group(
- thingGroupName=group_name_3d, parentGroupName=group_name_2b
- )
- thing_group3d.should.have.key("thingGroupName").which.should.equal(group_name_3d)
- thing_group3d.should.have.key("thingGroupArn")
+ tree_dict = {
+ group_name_1a: {
+ group_name_2a: {group_name_3a: {}, group_name_3b: {}},
+ group_name_2b: {group_name_3c: {}, group_name_3d: {}},
+ },
+ group_name_1b: {},
+ }
+ group_catalog = generate_thing_group_tree(client, tree_dict)
# describe groups
# groups level 1
@@ -910,7 +1018,7 @@ def test_describe_thing_group_metadata_hierarchy():
].should.match(group_name_1a)
thing_group_description2a["thingGroupMetadata"]["rootToParentThingGroups"][0][
"groupArn"
- ].should.match(thing_group1a["thingGroupArn"])
+ ].should.match(group_catalog[group_name_1a]["thingGroupArn"])
thing_group_description2a.should.have.key("version")
# 2b
thing_group_description2b = client.describe_thing_group(
@@ -936,7 +1044,7 @@ def test_describe_thing_group_metadata_hierarchy():
].should.match(group_name_1a)
thing_group_description2b["thingGroupMetadata"]["rootToParentThingGroups"][0][
"groupArn"
- ].should.match(thing_group1a["thingGroupArn"])
+ ].should.match(group_catalog[group_name_1a]["thingGroupArn"])
thing_group_description2b.should.have.key("version")
# groups level 3
# 3a
@@ -963,13 +1071,13 @@ def test_describe_thing_group_metadata_hierarchy():
].should.match(group_name_1a)
thing_group_description3a["thingGroupMetadata"]["rootToParentThingGroups"][0][
"groupArn"
- ].should.match(thing_group1a["thingGroupArn"])
+ ].should.match(group_catalog[group_name_1a]["thingGroupArn"])
thing_group_description3a["thingGroupMetadata"]["rootToParentThingGroups"][1][
"groupName"
].should.match(group_name_2a)
thing_group_description3a["thingGroupMetadata"]["rootToParentThingGroups"][1][
"groupArn"
- ].should.match(thing_group2a["thingGroupArn"])
+ ].should.match(group_catalog[group_name_2a]["thingGroupArn"])
thing_group_description3a.should.have.key("version")
# 3b
thing_group_description3b = client.describe_thing_group(
@@ -995,13 +1103,13 @@ def test_describe_thing_group_metadata_hierarchy():
].should.match(group_name_1a)
thing_group_description3b["thingGroupMetadata"]["rootToParentThingGroups"][0][
"groupArn"
- ].should.match(thing_group1a["thingGroupArn"])
+ ].should.match(group_catalog[group_name_1a]["thingGroupArn"])
thing_group_description3b["thingGroupMetadata"]["rootToParentThingGroups"][1][
"groupName"
].should.match(group_name_2a)
thing_group_description3b["thingGroupMetadata"]["rootToParentThingGroups"][1][
"groupArn"
- ].should.match(thing_group2a["thingGroupArn"])
+ ].should.match(group_catalog[group_name_2a]["thingGroupArn"])
thing_group_description3b.should.have.key("version")
# 3c
thing_group_description3c = client.describe_thing_group(
@@ -1027,13 +1135,13 @@ def test_describe_thing_group_metadata_hierarchy():
].should.match(group_name_1a)
thing_group_description3c["thingGroupMetadata"]["rootToParentThingGroups"][0][
"groupArn"
- ].should.match(thing_group1a["thingGroupArn"])
+ ].should.match(group_catalog[group_name_1a]["thingGroupArn"])
thing_group_description3c["thingGroupMetadata"]["rootToParentThingGroups"][1][
"groupName"
].should.match(group_name_2b)
thing_group_description3c["thingGroupMetadata"]["rootToParentThingGroups"][1][
"groupArn"
- ].should.match(thing_group2b["thingGroupArn"])
+ ].should.match(group_catalog[group_name_2b]["thingGroupArn"])
thing_group_description3c.should.have.key("version")
# 3d
thing_group_description3d = client.describe_thing_group(
@@ -1059,13 +1167,13 @@ def test_describe_thing_group_metadata_hierarchy():
].should.match(group_name_1a)
thing_group_description3d["thingGroupMetadata"]["rootToParentThingGroups"][0][
"groupArn"
- ].should.match(thing_group1a["thingGroupArn"])
+ ].should.match(group_catalog[group_name_1a]["thingGroupArn"])
thing_group_description3d["thingGroupMetadata"]["rootToParentThingGroups"][1][
"groupName"
].should.match(group_name_2b)
thing_group_description3d["thingGroupMetadata"]["rootToParentThingGroups"][1][
"groupArn"
- ].should.match(thing_group2b["thingGroupArn"])
+ ].should.match(group_catalog[group_name_2b]["thingGroupArn"])
thing_group_description3d.should.have.key("version")
diff --git a/tests/test_logs/test_logs.py b/tests/test_logs/test_logs.py
index 2429d7e93..675948150 100644
--- a/tests/test_logs/test_logs.py
+++ b/tests/test_logs/test_logs.py
@@ -1,10 +1,17 @@
+import base64
+import json
+import time
+import zlib
+from io import BytesIO
+from zipfile import ZipFile, ZIP_DEFLATED
+
import boto3
import os
import sure # noqa
import six
from botocore.exceptions import ClientError
-from moto import mock_logs, settings
+from moto import mock_logs, settings, mock_lambda, mock_iam
from nose.tools import assert_raises
from nose import SkipTest
@@ -425,3 +432,408 @@ def test_untag_log_group():
assert response["tags"] == remaining_tags
response = conn.delete_log_group(logGroupName=log_group_name)
+
+
+@mock_logs
+def test_describe_subscription_filters():
+ # given
+ client = boto3.client("logs", "us-east-1")
+ log_group_name = "/test"
+ client.create_log_group(logGroupName=log_group_name)
+
+ # when
+ response = client.describe_subscription_filters(logGroupName=log_group_name)
+
+ # then
+ response["subscriptionFilters"].should.have.length_of(0)
+
+
+@mock_logs
+def test_describe_subscription_filters_errors():
+ # given
+ client = boto3.client("logs", "us-east-1")
+
+ # when
+ with assert_raises(ClientError) as e:
+ client.describe_subscription_filters(logGroupName="not-existing-log-group",)
+
+ # then
+ ex = e.exception
+ ex.operation_name.should.equal("DescribeSubscriptionFilters")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
+ ex.response["Error"]["Message"].should.equal(
+ "The specified log group does not exist"
+ )
+
+
+@mock_lambda
+@mock_logs
+def test_put_subscription_filter_update():
+ # given
+ region_name = "us-east-1"
+ client_lambda = boto3.client("lambda", region_name)
+ client_logs = boto3.client("logs", region_name)
+ log_group_name = "/test"
+ log_stream_name = "stream"
+ client_logs.create_log_group(logGroupName=log_group_name)
+ client_logs.create_log_stream(
+ logGroupName=log_group_name, logStreamName=log_stream_name
+ )
+ function_arn = client_lambda.create_function(
+ FunctionName="test",
+ Runtime="python3.8",
+ Role=_get_role_name(region_name),
+ Handler="lambda_function.lambda_handler",
+ Code={"ZipFile": _get_test_zip_file()},
+ Description="test lambda function",
+ Timeout=3,
+ MemorySize=128,
+ Publish=True,
+ )["FunctionArn"]
+
+ # when
+ client_logs.put_subscription_filter(
+ logGroupName=log_group_name,
+ filterName="test",
+ filterPattern="",
+ destinationArn=function_arn,
+ )
+
+ # then
+ response = client_logs.describe_subscription_filters(logGroupName=log_group_name)
+ response["subscriptionFilters"].should.have.length_of(1)
+ filter = response["subscriptionFilters"][0]
+ creation_time = filter["creationTime"]
+ creation_time.should.be.a(int)
+ filter["destinationArn"] = "arn:aws:lambda:us-east-1:123456789012:function:test"
+ filter["distribution"] = "ByLogStream"
+ filter["logGroupName"] = "/test"
+ filter["filterName"] = "test"
+ filter["filterPattern"] = ""
+
+ # when
+ # to update an existing subscription filter the 'filerName' must be identical
+ client_logs.put_subscription_filter(
+ logGroupName=log_group_name,
+ filterName="test",
+ filterPattern="[]",
+ destinationArn=function_arn,
+ )
+
+ # then
+ response = client_logs.describe_subscription_filters(logGroupName=log_group_name)
+ response["subscriptionFilters"].should.have.length_of(1)
+ filter = response["subscriptionFilters"][0]
+ filter["creationTime"].should.equal(creation_time)
+ filter["destinationArn"] = "arn:aws:lambda:us-east-1:123456789012:function:test"
+ filter["distribution"] = "ByLogStream"
+ filter["logGroupName"] = "/test"
+ filter["filterName"] = "test"
+ filter["filterPattern"] = "[]"
+
+ # when
+ # only one subscription filter can be associated with a log group
+ with assert_raises(ClientError) as e:
+ client_logs.put_subscription_filter(
+ logGroupName=log_group_name,
+ filterName="test-2",
+ filterPattern="",
+ destinationArn=function_arn,
+ )
+
+ # then
+ ex = e.exception
+ ex.operation_name.should.equal("PutSubscriptionFilter")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("LimitExceededException")
+ ex.response["Error"]["Message"].should.equal("Resource limit exceeded.")
+
+
+@mock_lambda
+@mock_logs
+def test_put_subscription_filter_with_lambda():
+ # given
+ region_name = "us-east-1"
+ client_lambda = boto3.client("lambda", region_name)
+ client_logs = boto3.client("logs", region_name)
+ log_group_name = "/test"
+ log_stream_name = "stream"
+ client_logs.create_log_group(logGroupName=log_group_name)
+ client_logs.create_log_stream(
+ logGroupName=log_group_name, logStreamName=log_stream_name
+ )
+ function_arn = client_lambda.create_function(
+ FunctionName="test",
+ Runtime="python3.8",
+ Role=_get_role_name(region_name),
+ Handler="lambda_function.lambda_handler",
+ Code={"ZipFile": _get_test_zip_file()},
+ Description="test lambda function",
+ Timeout=3,
+ MemorySize=128,
+ Publish=True,
+ )["FunctionArn"]
+
+ # when
+ client_logs.put_subscription_filter(
+ logGroupName=log_group_name,
+ filterName="test",
+ filterPattern="",
+ destinationArn=function_arn,
+ )
+
+ # then
+ response = client_logs.describe_subscription_filters(logGroupName=log_group_name)
+ response["subscriptionFilters"].should.have.length_of(1)
+ filter = response["subscriptionFilters"][0]
+ filter["creationTime"].should.be.a(int)
+ filter["destinationArn"] = "arn:aws:lambda:us-east-1:123456789012:function:test"
+ filter["distribution"] = "ByLogStream"
+ filter["logGroupName"] = "/test"
+ filter["filterName"] = "test"
+ filter["filterPattern"] = ""
+
+ # when
+ client_logs.put_log_events(
+ logGroupName=log_group_name,
+ logStreamName=log_stream_name,
+ logEvents=[
+ {"timestamp": 0, "message": "test"},
+ {"timestamp": 0, "message": "test 2"},
+ ],
+ )
+
+ # then
+ msg_showed_up, received_message = _wait_for_log_msg(
+ client_logs, "/aws/lambda/test", "awslogs"
+ )
+ assert msg_showed_up, "CloudWatch log event was not found. All logs: {}".format(
+ received_message
+ )
+
+ data = json.loads(received_message)["awslogs"]["data"]
+ response = json.loads(
+ zlib.decompress(base64.b64decode(data), 16 + zlib.MAX_WBITS).decode("utf-8")
+ )
+ response["messageType"].should.equal("DATA_MESSAGE")
+ response["owner"].should.equal("123456789012")
+ response["logGroup"].should.equal("/test")
+ response["logStream"].should.equal("stream")
+ response["subscriptionFilters"].should.equal(["test"])
+ log_events = sorted(response["logEvents"], key=lambda log_event: log_event["id"])
+ log_events.should.have.length_of(2)
+ log_events[0]["id"].should.be.a(int)
+ log_events[0]["message"].should.equal("test")
+ log_events[0]["timestamp"].should.equal(0)
+ log_events[1]["id"].should.be.a(int)
+ log_events[1]["message"].should.equal("test 2")
+ log_events[1]["timestamp"].should.equal(0)
+
+
+@mock_logs
+def test_put_subscription_filter_errors():
+ # given
+ client = boto3.client("logs", "us-east-1")
+ log_group_name = "/test"
+ client.create_log_group(logGroupName=log_group_name)
+
+ # when
+ with assert_raises(ClientError) as e:
+ client.put_subscription_filter(
+ logGroupName="not-existing-log-group",
+ filterName="test",
+ filterPattern="",
+ destinationArn="arn:aws:lambda:us-east-1:123456789012:function:test",
+ )
+
+ # then
+ ex = e.exception
+ ex.operation_name.should.equal("PutSubscriptionFilter")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
+ ex.response["Error"]["Message"].should.equal(
+ "The specified log group does not exist"
+ )
+
+ # when
+ with assert_raises(ClientError) as e:
+ client.put_subscription_filter(
+ logGroupName="/test",
+ filterName="test",
+ filterPattern="",
+ destinationArn="arn:aws:lambda:us-east-1:123456789012:function:not-existing",
+ )
+
+ # then
+ ex = e.exception
+ ex.operation_name.should.equal("PutSubscriptionFilter")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("InvalidParameterException")
+ ex.response["Error"]["Message"].should.equal(
+ "Could not execute the lambda function. "
+ "Make sure you have given CloudWatch Logs permission to execute your function."
+ )
+
+ # when
+ with assert_raises(ClientError) as e:
+ client.put_subscription_filter(
+ logGroupName="/test",
+ filterName="test",
+ filterPattern="",
+ destinationArn="arn:aws:lambda:us-east-1:123456789012:function:not-existing",
+ )
+
+ # then
+ ex = e.exception
+ ex.operation_name.should.equal("PutSubscriptionFilter")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("InvalidParameterException")
+ ex.response["Error"]["Message"].should.equal(
+ "Could not execute the lambda function. "
+ "Make sure you have given CloudWatch Logs permission to execute your function."
+ )
+
+
+@mock_lambda
+@mock_logs
+def test_delete_subscription_filter_errors():
+ # given
+ region_name = "us-east-1"
+ client_lambda = boto3.client("lambda", region_name)
+ client_logs = boto3.client("logs", region_name)
+ log_group_name = "/test"
+ client_logs.create_log_group(logGroupName=log_group_name)
+ function_arn = client_lambda.create_function(
+ FunctionName="test",
+ Runtime="python3.8",
+ Role=_get_role_name(region_name),
+ Handler="lambda_function.lambda_handler",
+ Code={"ZipFile": _get_test_zip_file()},
+ Description="test lambda function",
+ Timeout=3,
+ MemorySize=128,
+ Publish=True,
+ )["FunctionArn"]
+ client_logs.put_subscription_filter(
+ logGroupName=log_group_name,
+ filterName="test",
+ filterPattern="",
+ destinationArn=function_arn,
+ )
+
+ # when
+ client_logs.delete_subscription_filter(
+ logGroupName="/test", filterName="test",
+ )
+
+ # then
+ response = client_logs.describe_subscription_filters(logGroupName=log_group_name)
+ response["subscriptionFilters"].should.have.length_of(0)
+
+
+@mock_lambda
+@mock_logs
+def test_delete_subscription_filter_errors():
+ # given
+ region_name = "us-east-1"
+ client_lambda = boto3.client("lambda", region_name)
+ client_logs = boto3.client("logs", region_name)
+ log_group_name = "/test"
+ client_logs.create_log_group(logGroupName=log_group_name)
+ function_arn = client_lambda.create_function(
+ FunctionName="test",
+ Runtime="python3.8",
+ Role=_get_role_name(region_name),
+ Handler="lambda_function.lambda_handler",
+ Code={"ZipFile": _get_test_zip_file()},
+ Description="test lambda function",
+ Timeout=3,
+ MemorySize=128,
+ Publish=True,
+ )["FunctionArn"]
+ client_logs.put_subscription_filter(
+ logGroupName=log_group_name,
+ filterName="test",
+ filterPattern="",
+ destinationArn=function_arn,
+ )
+
+ # when
+ with assert_raises(ClientError) as e:
+ client_logs.delete_subscription_filter(
+ logGroupName="not-existing-log-group", filterName="test",
+ )
+
+ # then
+ ex = e.exception
+ ex.operation_name.should.equal("DeleteSubscriptionFilter")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
+ ex.response["Error"]["Message"].should.equal(
+ "The specified log group does not exist"
+ )
+
+ # when
+ with assert_raises(ClientError) as e:
+ client_logs.delete_subscription_filter(
+ logGroupName="/test", filterName="wrong-filter-name",
+ )
+
+ # then
+ ex = e.exception
+ ex.operation_name.should.equal("DeleteSubscriptionFilter")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
+ ex.response["Error"]["Message"].should.equal(
+ "The specified subscription filter does not exist."
+ )
+
+
+def _get_role_name(region_name):
+ with mock_iam():
+ iam = boto3.client("iam", region_name=region_name)
+ try:
+ return iam.get_role(RoleName="test-role")["Role"]["Arn"]
+ except ClientError:
+ return iam.create_role(
+ RoleName="test-role", AssumeRolePolicyDocument="test policy", Path="/",
+ )["Role"]["Arn"]
+
+
+def _get_test_zip_file():
+ func_str = """
+def lambda_handler(event, context):
+ return event
+"""
+
+ zip_output = BytesIO()
+ zip_file = ZipFile(zip_output, "w", ZIP_DEFLATED)
+ zip_file.writestr("lambda_function.py", func_str)
+ zip_file.close()
+ zip_output.seek(0)
+ return zip_output.read()
+
+
+def _wait_for_log_msg(client, log_group_name, expected_msg_part):
+ received_messages = []
+ start = time.time()
+ while (time.time() - start) < 10:
+ result = client.describe_log_streams(logGroupName=log_group_name)
+ log_streams = result.get("logStreams")
+ if not log_streams:
+ time.sleep(1)
+ continue
+
+ for log_stream in log_streams:
+ result = client.get_log_events(
+ logGroupName=log_group_name, logStreamName=log_stream["logStreamName"],
+ )
+ received_messages.extend(
+ [event["message"] for event in result.get("events")]
+ )
+ for message in received_messages:
+ if expected_msg_part in message:
+ return True, message
+ time.sleep(1)
+ return False, received_messages
diff --git a/tests/test_managedblockchain/__init__.py b/tests/test_managedblockchain/__init__.py
new file mode 100644
index 000000000..baffc4882
--- /dev/null
+++ b/tests/test_managedblockchain/__init__.py
@@ -0,0 +1 @@
+from __future__ import unicode_literals
diff --git a/tests/test_managedblockchain/helpers.py b/tests/test_managedblockchain/helpers.py
new file mode 100644
index 000000000..38c13b512
--- /dev/null
+++ b/tests/test_managedblockchain/helpers.py
@@ -0,0 +1,67 @@
+from __future__ import unicode_literals
+
+
+default_frameworkconfiguration = {"Fabric": {"Edition": "STARTER"}}
+
+default_votingpolicy = {
+ "ApprovalThresholdPolicy": {
+ "ThresholdPercentage": 50,
+ "ProposalDurationInHours": 24,
+ "ThresholdComparator": "GREATER_THAN_OR_EQUAL_TO",
+ }
+}
+
+default_memberconfiguration = {
+ "Name": "testmember1",
+ "Description": "Test Member 1",
+ "FrameworkConfiguration": {
+ "Fabric": {"AdminUsername": "admin", "AdminPassword": "Admin12345"}
+ },
+ "LogPublishingConfiguration": {
+ "Fabric": {"CaLogs": {"Cloudwatch": {"Enabled": False}}}
+ },
+}
+
+default_policy_actions = {"Invitations": [{"Principal": "123456789012"}]}
+
+multiple_policy_actions = {
+ "Invitations": [{"Principal": "123456789012"}, {"Principal": "123456789013"}]
+}
+
+
+def member_id_exist_in_list(members, memberid):
+ memberidxists = False
+ for member in members:
+ if member["Id"] == memberid:
+ memberidxists = True
+ break
+ return memberidxists
+
+
+def create_member_configuration(
+ name, adminuser, adminpass, cloudwatchenabled, description=None
+):
+ d = {
+ "Name": name,
+ "FrameworkConfiguration": {
+ "Fabric": {"AdminUsername": adminuser, "AdminPassword": adminpass}
+ },
+ "LogPublishingConfiguration": {
+ "Fabric": {"CaLogs": {"Cloudwatch": {"Enabled": cloudwatchenabled}}}
+ },
+ }
+
+ if description is not None:
+ d["Description"] = description
+
+ return d
+
+
+def select_invitation_id_for_network(invitations, networkid, status=None):
+ # Get invitations based on network and maybe status
+ invitationsfornetwork = []
+ for invitation in invitations:
+ if invitation["NetworkSummary"]["Id"] == networkid:
+ if status is None or invitation["Status"] == status:
+ invitationsfornetwork.append(invitation["InvitationId"])
+ return invitationsfornetwork
diff --git a/tests/test_managedblockchain/test_managedblockchain_invitations.py b/tests/test_managedblockchain/test_managedblockchain_invitations.py
new file mode 100644
index 000000000..81b20a9ba
--- /dev/null
+++ b/tests/test_managedblockchain/test_managedblockchain_invitations.py
@@ -0,0 +1,142 @@
+from __future__ import unicode_literals
+
+import boto3
+import sure # noqa
+
+from moto.managedblockchain.exceptions import BadRequestException
+from moto import mock_managedblockchain
+from . import helpers
+
+
+@mock_managedblockchain
+def test_create_2_invitations():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.multiple_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ response["Invitations"].should.have.length_of(2)
+ response["Invitations"][0]["NetworkSummary"]["Id"].should.equal(network_id)
+ response["Invitations"][0]["Status"].should.equal("PENDING")
+ response["Invitations"][1]["NetworkSummary"]["Id"].should.equal(network_id)
+ response["Invitations"][1]["Status"].should.equal("PENDING")
+
+
+@mock_managedblockchain
+def test_reject_invitation():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ response["Invitations"][0]["NetworkSummary"]["Id"].should.equal(network_id)
+ response["Invitations"][0]["Status"].should.equal("PENDING")
+ invitation_id = response["Invitations"][0]["InvitationId"]
+
+ # Reject - thanks but no thanks
+ response = conn.reject_invitation(InvitationId=invitation_id)
+
+ # Check the invitation status
+ response = conn.list_invitations()
+ response["Invitations"][0]["InvitationId"].should.equal(invitation_id)
+ response["Invitations"][0]["Status"].should.equal("REJECTED")
+
+
+@mock_managedblockchain
+def test_reject_invitation_badinvitation():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ response = conn.reject_invitation.when.called_with(
+ InvitationId="in-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "InvitationId in-ABCDEFGHIJKLMNOP0123456789 not found.")
diff --git a/tests/test_managedblockchain/test_managedblockchain_members.py b/tests/test_managedblockchain/test_managedblockchain_members.py
new file mode 100644
index 000000000..76d29dd55
--- /dev/null
+++ b/tests/test_managedblockchain/test_managedblockchain_members.py
@@ -0,0 +1,669 @@
+from __future__ import unicode_literals
+
+import boto3
+import sure # noqa
+
+from moto.managedblockchain.exceptions import BadRequestException
+from moto import mock_managedblockchain
+from . import helpers
+
+
+@mock_managedblockchain
+def test_create_another_member():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Description="Test Network 1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ response["Invitations"][0]["NetworkSummary"]["Id"].should.equal(network_id)
+ response["Invitations"][0]["Status"].should.equal("PENDING")
+ invitation_id = response["Invitations"][0]["InvitationId"]
+
+ # Create the member
+ response = conn.create_member(
+ InvitationId=invitation_id,
+ NetworkId=network_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False
+ ),
+ )
+ member_id2 = response["MemberId"]
+
+ # Check the invitation status
+ response = conn.list_invitations()
+ response["Invitations"][0]["InvitationId"].should.equal(invitation_id)
+ response["Invitations"][0]["Status"].should.equal("ACCEPTED")
+
+ # Find member in full list
+ response = conn.list_members(NetworkId=network_id)
+ members = response["Members"]
+ members.should.have.length_of(2)
+ helpers.member_id_exist_in_list(members, member_id2).should.equal(True)
+
+ # Get member 2 details
+ response = conn.get_member(NetworkId=network_id, MemberId=member_id2)
+ response["Member"]["Name"].should.equal("testmember2")
+
+ # Update member
+ logconfignewenabled = not helpers.default_memberconfiguration[
+ "LogPublishingConfiguration"
+ ]["Fabric"]["CaLogs"]["Cloudwatch"]["Enabled"]
+ logconfignew = {
+ "Fabric": {"CaLogs": {"Cloudwatch": {"Enabled": logconfignewenabled}}}
+ }
+ conn.update_member(
+ NetworkId=network_id,
+ MemberId=member_id2,
+ LogPublishingConfiguration=logconfignew,
+ )
+
+ # Get member 2 details
+ response = conn.get_member(NetworkId=network_id, MemberId=member_id2)
+ response["Member"]["LogPublishingConfiguration"]["Fabric"]["CaLogs"]["Cloudwatch"][
+ "Enabled"
+ ].should.equal(logconfignewenabled)
+
+
+@mock_managedblockchain
+def test_create_another_member_withopts():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ response["Invitations"][0]["NetworkSummary"]["Id"].should.equal(network_id)
+ response["Invitations"][0]["Status"].should.equal("PENDING")
+ invitation_id = response["Invitations"][0]["InvitationId"]
+
+ # Create the member
+ response = conn.create_member(
+ InvitationId=invitation_id,
+ NetworkId=network_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False, "Test Member 2"
+ ),
+ )
+ member_id2 = response["MemberId"]
+
+ # Check the invitation status
+ response = conn.list_invitations()
+ response["Invitations"][0]["InvitationId"].should.equal(invitation_id)
+ response["Invitations"][0]["Status"].should.equal("ACCEPTED")
+
+ # Find member in full list
+ response = conn.list_members(NetworkId=network_id)
+ members = response["Members"]
+ members.should.have.length_of(2)
+ helpers.member_id_exist_in_list(members, member_id2).should.equal(True)
+
+ # Get member 2 details
+ response = conn.get_member(NetworkId=network_id, MemberId=member_id2)
+ response["Member"]["Description"].should.equal("Test Member 2")
+
+ # Try to create member with already used invitation
+ response = conn.create_member.when.called_with(
+ InvitationId=invitation_id,
+ NetworkId=network_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False, "Test Member 2 Duplicate"
+ ),
+ ).should.throw(Exception, "Invitation {0} not valid".format(invitation_id))
+
+ # Delete member 2
+ conn.delete_member(NetworkId=network_id, MemberId=member_id2)
+
+ # Member is still in the list
+ response = conn.list_members(NetworkId=network_id)
+ members = response["Members"]
+ members.should.have.length_of(2)
+
+ # But cannot get
+ response = conn.get_member.when.called_with(
+ NetworkId=network_id, MemberId=member_id2,
+ ).should.throw(Exception, "Member {0} not found".format(member_id2))
+
+ # Delete member 1
+ conn.delete_member(NetworkId=network_id, MemberId=member_id)
+
+ # Network should be gone
+ response = conn.list_networks()
+ mbcnetworks = response["Networks"]
+ mbcnetworks.should.have.length_of(0)
+
+ # Verify the invitation network status is DELETED
+ # Get the invitation
+ response = conn.list_invitations()
+ response["Invitations"].should.have.length_of(1)
+ response["Invitations"][0]["NetworkSummary"]["Id"].should.equal(network_id)
+ response["Invitations"][0]["NetworkSummary"]["Status"].should.equal("DELETED")
+
+
+@mock_managedblockchain
+def test_create_and_delete_member():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create proposal (create additional member)
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ invitation_id = response["Invitations"][0]["InvitationId"]
+
+ # Create the member
+ response = conn.create_member(
+ InvitationId=invitation_id,
+ NetworkId=network_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False, "Test Member 2"
+ ),
+ )
+ member_id2 = response["MemberId"]
+
+ both_policy_actions = {
+ "Invitations": [{"Principal": "123456789012"}],
+ "Removals": [{"MemberId": member_id2}],
+ }
+
+ # Create proposal (invite and remove member)
+ response = conn.create_proposal(
+ NetworkId=network_id, MemberId=member_id, Actions=both_policy_actions,
+ )
+ proposal_id2 = response["ProposalId"]
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id2)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id2,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Check the invitation status
+ response = conn.list_invitations()
+ invitations = helpers.select_invitation_id_for_network(
+ response["Invitations"], network_id, "PENDING"
+ )
+ invitations.should.have.length_of(1)
+
+ # Member is still in the list
+ response = conn.list_members(NetworkId=network_id)
+ members = response["Members"]
+ members.should.have.length_of(2)
+ foundmember2 = False
+ for member in members:
+ if member["Id"] == member_id2 and member["Status"] == "DELETED":
+ foundmember2 = True
+ foundmember2.should.equal(True)
+
+
+@mock_managedblockchain
+def test_create_too_many_members():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create 4 more members - create invitations for 5
+ for counter in range(2, 7):
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ for counter in range(2, 6):
+ # Get the invitation
+ response = conn.list_invitations()
+ invitation_id = helpers.select_invitation_id_for_network(
+ response["Invitations"], network_id, "PENDING"
+ )[0]
+
+ # Create the member
+ response = conn.create_member(
+ InvitationId=invitation_id,
+ NetworkId=network_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember" + str(counter),
+ "admin",
+ "Admin12345",
+ False,
+ "Test Member " + str(counter),
+ ),
+ )
+ member_id = response["MemberId"]
+
+ # Find member in full list
+ response = conn.list_members(NetworkId=network_id)
+ members = response["Members"]
+ members.should.have.length_of(counter)
+ helpers.member_id_exist_in_list(members, member_id).should.equal(True)
+
+ # Get member details
+ response = conn.get_member(NetworkId=network_id, MemberId=member_id)
+ response["Member"]["Description"].should.equal("Test Member " + str(counter))
+
+ # Try to create the sixth
+ response = conn.list_invitations()
+ invitation_id = helpers.select_invitation_id_for_network(
+ response["Invitations"], network_id, "PENDING"
+ )[0]
+
+ # Try to create member with already used invitation
+ response = conn.create_member.when.called_with(
+ InvitationId=invitation_id,
+ NetworkId=network_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember6", "admin", "Admin12345", False, "Test Member 6"
+ ),
+ ).should.throw(
+ Exception,
+ "5 is the maximum number of members allowed in a STARTER Edition network",
+ )
+
+
+@mock_managedblockchain
+def test_create_another_member_alreadyhave():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Description="Test Network 1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ invitation_id = response["Invitations"][0]["InvitationId"]
+
+ # Should fail trying to create with same name
+ response = conn.create_member.when.called_with(
+ NetworkId=network_id,
+ InvitationId=invitation_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember1", "admin", "Admin12345", False
+ ),
+ ).should.throw(
+ Exception,
+ "Member name {0} already exists in network {1}".format(
+ "testmember1", network_id
+ ),
+ )
+
+
+@mock_managedblockchain
+def test_create_another_member_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.create_member.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ InvitationId="id-ABCDEFGHIJKLMNOP0123456789",
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False
+ ),
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_create_another_member_badinvitation():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+
+ response = conn.create_member.when.called_with(
+ NetworkId=network_id,
+ InvitationId="in-ABCDEFGHIJKLMNOP0123456789",
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False
+ ),
+ ).should.throw(Exception, "Invitation in-ABCDEFGHIJKLMNOP0123456789 not valid")
+
+
+@mock_managedblockchain
+def test_create_another_member_adminpassword():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ invitation_id = response["Invitations"][0]["InvitationId"]
+
+ badadminpassmemberconf = helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False
+ )
+
+ # Too short
+ badadminpassmemberconf["FrameworkConfiguration"]["Fabric"][
+ "AdminPassword"
+ ] = "badap"
+ response = conn.create_member.when.called_with(
+ NetworkId=network_id,
+ InvitationId=invitation_id,
+ MemberConfiguration=badadminpassmemberconf,
+ ).should.throw(
+ Exception,
+ "Invalid length for parameter MemberConfiguration.FrameworkConfiguration.Fabric.AdminPassword",
+ )
+
+ # No uppercase or numbers
+ badadminpassmemberconf["FrameworkConfiguration"]["Fabric"][
+ "AdminPassword"
+ ] = "badadminpwd"
+ response = conn.create_member.when.called_with(
+ NetworkId=network_id,
+ InvitationId=invitation_id,
+ MemberConfiguration=badadminpassmemberconf,
+ ).should.throw(Exception, "Invalid request body")
+
+ # No lowercase or numbers
+ badadminpassmemberconf["FrameworkConfiguration"]["Fabric"][
+ "AdminPassword"
+ ] = "BADADMINPWD"
+ response = conn.create_member.when.called_with(
+ NetworkId=network_id,
+ InvitationId=invitation_id,
+ MemberConfiguration=badadminpassmemberconf,
+ ).should.throw(Exception, "Invalid request body")
+
+ # No numbers
+ badadminpassmemberconf["FrameworkConfiguration"]["Fabric"][
+ "AdminPassword"
+ ] = "badAdminpwd"
+ response = conn.create_member.when.called_with(
+ NetworkId=network_id,
+ InvitationId=invitation_id,
+ MemberConfiguration=badadminpassmemberconf,
+ ).should.throw(Exception, "Invalid request body")
+
+ # Invalid character
+ badadminpassmemberconf["FrameworkConfiguration"]["Fabric"][
+ "AdminPassword"
+ ] = "badAdmin@pwd1"
+ response = conn.create_member.when.called_with(
+ NetworkId=network_id,
+ InvitationId=invitation_id,
+ MemberConfiguration=badadminpassmemberconf,
+ ).should.throw(Exception, "Invalid request body")
+
+
+@mock_managedblockchain
+def test_list_members_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.list_members.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_get_member_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.get_member.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ MemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_get_member_badmember():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+
+ response = conn.get_member.when.called_with(
+ NetworkId=network_id, MemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Member m-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_delete_member_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.delete_member.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ MemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_delete_member_badmember():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+
+ response = conn.delete_member.when.called_with(
+ NetworkId=network_id, MemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Member m-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_update_member_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.update_member.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ MemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ LogPublishingConfiguration=helpers.default_memberconfiguration[
+ "LogPublishingConfiguration"
+ ],
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_update_member_badmember():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+
+ response = conn.update_member.when.called_with(
+ NetworkId=network_id,
+ MemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ LogPublishingConfiguration=helpers.default_memberconfiguration[
+ "LogPublishingConfiguration"
+ ],
+ ).should.throw(Exception, "Member m-ABCDEFGHIJKLMNOP0123456789 not found")
diff --git a/tests/test_managedblockchain/test_managedblockchain_networks.py b/tests/test_managedblockchain/test_managedblockchain_networks.py
new file mode 100644
index 000000000..4e1579017
--- /dev/null
+++ b/tests/test_managedblockchain/test_managedblockchain_networks.py
@@ -0,0 +1,123 @@
+from __future__ import unicode_literals
+
+import boto3
+import sure # noqa
+
+from moto.managedblockchain.exceptions import BadRequestException
+from moto import mock_managedblockchain
+from . import helpers
+
+
+@mock_managedblockchain
+def test_create_network():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+ network_id.should.match("n-[A-Z0-9]{26}")
+ member_id.should.match("m-[A-Z0-9]{26}")
+
+ # Find in full list
+ response = conn.list_networks()
+ mbcnetworks = response["Networks"]
+ mbcnetworks.should.have.length_of(1)
+ mbcnetworks[0]["Name"].should.equal("testnetwork1")
+
+ # Get network details
+ response = conn.get_network(NetworkId=network_id)
+ response["Network"]["Name"].should.equal("testnetwork1")
+
+
+@mock_managedblockchain
+def test_create_network_withopts():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.create_network(
+ Name="testnetwork1",
+ Description="Test Network 1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+ network_id.should.match("n-[A-Z0-9]{26}")
+ member_id.should.match("m-[A-Z0-9]{26}")
+
+ # Find in full list
+ response = conn.list_networks()
+ mbcnetworks = response["Networks"]
+ mbcnetworks.should.have.length_of(1)
+ mbcnetworks[0]["Description"].should.equal("Test Network 1")
+
+ # Get network details
+ response = conn.get_network(NetworkId=network_id)
+ response["Network"]["Description"].should.equal("Test Network 1")
+
+
+@mock_managedblockchain
+def test_create_network_noframework():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.create_network.when.called_with(
+ Name="testnetwork1",
+ Description="Test Network 1",
+ Framework="HYPERLEDGER_VINYL",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ ).should.throw(Exception, "Invalid request body")
+
+
+@mock_managedblockchain
+def test_create_network_badframeworkver():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.create_network.when.called_with(
+ Name="testnetwork1",
+ Description="Test Network 1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.X",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ ).should.throw(
+ Exception, "Invalid version 1.X requested for framework HYPERLEDGER_FABRIC"
+ )
+
+
+@mock_managedblockchain
+def test_create_network_badedition():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ frameworkconfiguration = {"Fabric": {"Edition": "SUPER"}}
+
+ response = conn.create_network.when.called_with(
+ Name="testnetwork1",
+ Description="Test Network 1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ ).should.throw(Exception, "Invalid request body")
+
+
+@mock_managedblockchain
+def test_get_network_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.get_network.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
diff --git a/tests/test_managedblockchain/test_managedblockchain_proposals.py b/tests/test_managedblockchain/test_managedblockchain_proposals.py
new file mode 100644
index 000000000..407d26246
--- /dev/null
+++ b/tests/test_managedblockchain/test_managedblockchain_proposals.py
@@ -0,0 +1,199 @@
+from __future__ import unicode_literals
+
+import boto3
+import sure # noqa
+
+from moto.managedblockchain.exceptions import BadRequestException
+from moto import mock_managedblockchain
+from . import helpers
+
+
+@mock_managedblockchain
+def test_create_proposal():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+ network_id.should.match("n-[A-Z0-9]{26}")
+ member_id.should.match("m-[A-Z0-9]{26}")
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+ proposal_id.should.match("p-[A-Z0-9]{26}")
+
+ # Find in full list
+ response = conn.list_proposals(NetworkId=network_id)
+ proposals = response["Proposals"]
+ proposals.should.have.length_of(1)
+ proposals[0]["ProposalId"].should.equal(proposal_id)
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+
+
+@mock_managedblockchain
+def test_create_proposal_withopts():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+ network_id.should.match("n-[A-Z0-9]{26}")
+ member_id.should.match("m-[A-Z0-9]{26}")
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ Description="Adding a new member",
+ )
+ proposal_id = response["ProposalId"]
+ proposal_id.should.match("p-[A-Z0-9]{26}")
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["Description"].should.equal("Adding a new member")
+
+
+@mock_managedblockchain
+def test_create_proposal_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.create_proposal.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ MemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ Actions=helpers.default_policy_actions,
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_create_proposal_badmember():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+
+ response = conn.create_proposal.when.called_with(
+ NetworkId=network_id,
+ MemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ Actions=helpers.default_policy_actions,
+ ).should.throw(Exception, "Member m-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_create_proposal_badinvitationacctid():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Must be 12 digits
+ actions = {"Invitations": [{"Principal": "1234567890"}]}
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.create_proposal.when.called_with(
+ NetworkId=network_id, MemberId=member_id, Actions=actions,
+ ).should.throw(Exception, "Account ID format specified in proposal is not valid")
+
+
+@mock_managedblockchain
+def test_create_proposal_badremovalmemid():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Must be 12 digits
+ actions = {"Removals": [{"MemberId": "m-ABCDEFGHIJKLMNOP0123456789"}]}
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.create_proposal.when.called_with(
+ NetworkId=network_id, MemberId=member_id, Actions=actions,
+ ).should.throw(Exception, "Member ID format specified in proposal is not valid")
+
+
+@mock_managedblockchain
+def test_list_proposal_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.list_proposals.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_get_proposal_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.get_proposal.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ ProposalId="p-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_get_proposal_badproposal():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+
+ response = conn.get_proposal.when.called_with(
+ NetworkId=network_id, ProposalId="p-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Proposal p-ABCDEFGHIJKLMNOP0123456789 not found")
diff --git a/tests/test_managedblockchain/test_managedblockchain_proposalvotes.py b/tests/test_managedblockchain/test_managedblockchain_proposalvotes.py
new file mode 100644
index 000000000..a026b496f
--- /dev/null
+++ b/tests/test_managedblockchain/test_managedblockchain_proposalvotes.py
@@ -0,0 +1,529 @@
+from __future__ import unicode_literals
+
+import os
+
+import boto3
+import sure # noqa
+from freezegun import freeze_time
+from nose import SkipTest
+
+from moto.managedblockchain.exceptions import BadRequestException
+from moto import mock_managedblockchain, settings
+from . import helpers
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_one_member_total_yes():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # List proposal votes
+ response = conn.list_proposal_votes(NetworkId=network_id, ProposalId=proposal_id)
+ response["ProposalVotes"][0]["MemberId"].should.equal(member_id)
+
+ # Get proposal details - should be APPROVED
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["Status"].should.equal("APPROVED")
+ response["Proposal"]["YesVoteCount"].should.equal(1)
+ response["Proposal"]["NoVoteCount"].should.equal(0)
+ response["Proposal"]["OutstandingVoteCount"].should.equal(0)
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_one_member_total_no():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ # Create proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+ proposal_id = response["ProposalId"]
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+ # Vote no
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="NO",
+ )
+
+ # List proposal votes
+ response = conn.list_proposal_votes(NetworkId=network_id, ProposalId=proposal_id)
+ response["ProposalVotes"][0]["MemberId"].should.equal(member_id)
+
+ # Get proposal details - should be REJECTED
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["Status"].should.equal("REJECTED")
+ response["Proposal"]["YesVoteCount"].should.equal(0)
+ response["Proposal"]["NoVoteCount"].should.equal(1)
+ response["Proposal"]["OutstandingVoteCount"].should.equal(0)
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_yes_greater_than():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ votingpolicy = {
+ "ApprovalThresholdPolicy": {
+ "ThresholdPercentage": 50,
+ "ProposalDurationInHours": 24,
+ "ThresholdComparator": "GREATER_THAN",
+ }
+ }
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ invitation_id = response["Invitations"][0]["InvitationId"]
+
+ # Create the member
+ response = conn.create_member(
+ InvitationId=invitation_id,
+ NetworkId=network_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False, "Test Member 2"
+ ),
+ )
+ member_id2 = response["MemberId"]
+
+ # Create another proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ # Vote yes with member 1
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_no_greater_than():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ votingpolicy = {
+ "ApprovalThresholdPolicy": {
+ "ThresholdPercentage": 50,
+ "ProposalDurationInHours": 24,
+ "ThresholdComparator": "GREATER_THAN",
+ }
+ }
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ invitation_id = response["Invitations"][0]["InvitationId"]
+
+ # Create the member
+ response = conn.create_member(
+ InvitationId=invitation_id,
+ NetworkId=network_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False, "Test Member 2"
+ ),
+ )
+ member_id2 = response["MemberId"]
+
+ # Create another proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ # Vote no with member 1
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="NO",
+ )
+
+ # Vote no with member 2
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id2,
+ Vote="NO",
+ )
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("REJECTED")
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_expiredproposal():
+ if os.environ.get("TEST_SERVER_MODE", "false").lower() == "true":
+ raise SkipTest("Cant manipulate time in server mode")
+
+ votingpolicy = {
+ "ApprovalThresholdPolicy": {
+ "ThresholdPercentage": 50,
+ "ProposalDurationInHours": 1,
+ "ThresholdComparator": "GREATER_THAN_OR_EQUAL_TO",
+ }
+ }
+
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ with freeze_time("2015-01-01 12:00:00"):
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ with freeze_time("2015-02-01 12:00:00"):
+ # Vote yes - should set status to expired
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get proposal details - should be EXPIRED
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["Status"].should.equal("EXPIRED")
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.vote_on_proposal.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ ProposalId="p-ABCDEFGHIJKLMNOP0123456789",
+ VoterMemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ Vote="YES",
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_badproposal():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+
+ response = conn.vote_on_proposal.when.called_with(
+ NetworkId=network_id,
+ ProposalId="p-ABCDEFGHIJKLMNOP0123456789",
+ VoterMemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ Vote="YES",
+ ).should.throw(Exception, "Proposal p-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_badmember():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ response = conn.vote_on_proposal.when.called_with(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId="m-ABCDEFGHIJKLMNOP0123456789",
+ Vote="YES",
+ ).should.throw(Exception, "Member m-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_badvote():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ response = conn.vote_on_proposal.when.called_with(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="FOO",
+ ).should.throw(Exception, "Invalid request body")
+
+
+@mock_managedblockchain
+def test_vote_on_proposal_alreadyvoted():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network - need a good network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ # Vote yes
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Get the invitation
+ response = conn.list_invitations()
+ invitation_id = response["Invitations"][0]["InvitationId"]
+
+ # Create the member
+ response = conn.create_member(
+ InvitationId=invitation_id,
+ NetworkId=network_id,
+ MemberConfiguration=helpers.create_member_configuration(
+ "testmember2", "admin", "Admin12345", False, "Test Member 2"
+ ),
+ )
+ member_id2 = response["MemberId"]
+
+ # Create another proposal
+ response = conn.create_proposal(
+ NetworkId=network_id,
+ MemberId=member_id,
+ Actions=helpers.default_policy_actions,
+ )
+
+ proposal_id = response["ProposalId"]
+
+ # Get proposal details
+ response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
+ response["Proposal"]["NetworkId"].should.equal(network_id)
+ response["Proposal"]["Status"].should.equal("IN_PROGRESS")
+
+ # Vote yes with member 1
+ response = conn.vote_on_proposal(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ )
+
+ # Vote yes with member 1 again
+ response = conn.vote_on_proposal.when.called_with(
+ NetworkId=network_id,
+ ProposalId=proposal_id,
+ VoterMemberId=member_id,
+ Vote="YES",
+ ).should.throw(Exception, "Invalid request body")
+
+
+@mock_managedblockchain
+def test_list_proposal_votes_badnetwork():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ response = conn.list_proposal_votes.when.called_with(
+ NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
+ ProposalId="p-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Network n-ABCDEFGHIJKLMNOP0123456789 not found")
+
+
+@mock_managedblockchain
+def test_list_proposal_votes_badproposal():
+ conn = boto3.client("managedblockchain", region_name="us-east-1")
+
+ # Create network
+ response = conn.create_network(
+ Name="testnetwork1",
+ Framework="HYPERLEDGER_FABRIC",
+ FrameworkVersion="1.2",
+ FrameworkConfiguration=helpers.default_frameworkconfiguration,
+ VotingPolicy=helpers.default_votingpolicy,
+ MemberConfiguration=helpers.default_memberconfiguration,
+ )
+ network_id = response["NetworkId"]
+ member_id = response["MemberId"]
+
+ response = conn.list_proposal_votes.when.called_with(
+ NetworkId=network_id, ProposalId="p-ABCDEFGHIJKLMNOP0123456789",
+ ).should.throw(Exception, "Proposal p-ABCDEFGHIJKLMNOP0123456789 not found")
diff --git a/tests/test_opsworks/test_instances.py b/tests/test_opsworks/test_instances.py
index 5f0dc2040..93935d20f 100644
--- a/tests/test_opsworks/test_instances.py
+++ b/tests/test_opsworks/test_instances.py
@@ -195,6 +195,10 @@ def test_ec2_integration():
reservations = ec2.describe_instances()["Reservations"]
assert reservations.should.be.empty
+ # Before starting the instance, its status should be "stopped"
+ opsworks_instance = opsworks.describe_instances(StackId=stack_id)["Instances"][0]
+ opsworks_instance["Status"].should.equal("stopped")
+
# After starting the instance, it should be discoverable via ec2
opsworks.start_instance(InstanceId=instance_id)
reservations = ec2.describe_instances()["Reservations"]
@@ -204,3 +208,5 @@ def test_ec2_integration():
instance["InstanceId"].should.equal(opsworks_instance["Ec2InstanceId"])
instance["PrivateIpAddress"].should.equal(opsworks_instance["PrivateIp"])
+ # After starting the instance, its status should be "online"
+ opsworks_instance["Status"].should.equal("online")
diff --git a/tests/test_redshift/test_redshift.py b/tests/test_redshift/test_redshift.py
index 6bb3b1396..cf96ee15f 100644
--- a/tests/test_redshift/test_redshift.py
+++ b/tests/test_redshift/test_redshift.py
@@ -915,6 +915,11 @@ def test_create_cluster_from_snapshot():
ClusterIdentifier=original_cluster_identifier,
)
+ client.restore_from_cluster_snapshot.when.called_with(
+ ClusterIdentifier=original_cluster_identifier,
+ SnapshotIdentifier=original_snapshot_identifier,
+ ).should.throw(ClientError, "ClusterAlreadyExists")
+
response = client.restore_from_cluster_snapshot(
ClusterIdentifier=new_cluster_identifier,
SnapshotIdentifier=original_snapshot_identifier,
@@ -1333,3 +1338,20 @@ def test_modify_snapshot_copy_retention_period():
response = client.describe_clusters(ClusterIdentifier="test")
cluster_snapshot_copy_status = response["Clusters"][0]["ClusterSnapshotCopyStatus"]
cluster_snapshot_copy_status["RetentionPeriod"].should.equal(5)
+
+
+@mock_redshift
+def test_create_duplicate_cluster_fails():
+ kwargs = {
+ "ClusterIdentifier": "test",
+ "ClusterType": "single-node",
+ "DBName": "test",
+ "MasterUsername": "user",
+ "MasterUserPassword": "password",
+ "NodeType": "ds2.xlarge",
+ }
+ client = boto3.client("redshift", region_name="us-east-1")
+ client.create_cluster(**kwargs)
+ client.create_cluster.when.called_with(**kwargs).should.throw(
+ ClientError, "ClusterAlreadyExists"
+ )
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
index f60e0293e..bcb9da87f 100644
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -2149,6 +2149,19 @@ def test_boto3_copy_object_with_versioning():
data.should.equal(b"test2")
+@mock_s3
+def test_s3_abort_multipart_data_with_invalid_upload_and_key():
+ client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
+
+ client.create_bucket(Bucket="blah")
+
+ with assert_raises(Exception) as err:
+ client.abort_multipart_upload(
+ Bucket="blah", Key="foobar", UploadId="dummy_upload_id"
+ )
+ err.exception.response["Error"]["Code"].should.equal("NoSuchUpload")
+
+
@mock_s3
def test_boto3_copy_object_from_unversioned_to_versioned_bucket():
client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
diff --git a/tests/test_ses/test_ses.py b/tests/test_ses/test_ses.py
index 851327b9d..ce0062974 100644
--- a/tests/test_ses/test_ses.py
+++ b/tests/test_ses/test_ses.py
@@ -127,3 +127,53 @@ def test_send_raw_email():
send_quota["GetSendQuotaResponse"]["GetSendQuotaResult"]["SentLast24Hours"]
)
sent_count.should.equal(1)
+
+
+@mock_ses_deprecated
+def test_get_send_statistics():
+ conn = boto.connect_ses("the_key", "the_secret")
+
+ conn.send_email.when.called_with(
+ "test@example.com",
+ "test subject",
+ "test body",
+ "test_to@example.com",
+ format="html",
+ ).should.throw(BotoServerError)
+
+ # tests to verify rejects in get_send_statistics
+ result = conn.get_send_statistics()
+
+ reject_count = int(
+ result["GetSendStatisticsResponse"]["GetSendStatisticsResult"][
+ "SendDataPoints"
+ ][0]["Rejects"]
+ )
+ delivery_count = int(
+ result["GetSendStatisticsResponse"]["GetSendStatisticsResult"][
+ "SendDataPoints"
+ ][0]["DeliveryAttempts"]
+ )
+ reject_count.should.equal(1)
+ delivery_count.should.equal(0)
+
+ conn.verify_email_identity("test@example.com")
+ conn.send_email(
+ "test@example.com", "test subject", "test body", "test_to@example.com"
+ )
+
+ # tests to delivery attempts in get_send_statistics
+ result = conn.get_send_statistics()
+
+ reject_count = int(
+ result["GetSendStatisticsResponse"]["GetSendStatisticsResult"][
+ "SendDataPoints"
+ ][0]["Rejects"]
+ )
+ delivery_count = int(
+ result["GetSendStatisticsResponse"]["GetSendStatisticsResult"][
+ "SendDataPoints"
+ ][0]["DeliveryAttempts"]
+ )
+ reject_count.should.equal(1)
+ delivery_count.should.equal(1)
diff --git a/tests/test_ses/test_ses_boto3.py b/tests/test_ses/test_ses_boto3.py
index de8aa0813..707afe8fb 100644
--- a/tests/test_ses/test_ses_boto3.py
+++ b/tests/test_ses/test_ses_boto3.py
@@ -4,6 +4,8 @@ import boto3
from botocore.exceptions import ClientError
from six.moves.email_mime_multipart import MIMEMultipart
from six.moves.email_mime_text import MIMEText
+from nose.tools import assert_raises
+
import sure # noqa
@@ -139,19 +141,7 @@ def test_send_html_email():
def test_send_raw_email():
conn = boto3.client("ses", region_name="us-east-1")
- message = MIMEMultipart()
- message["Subject"] = "Test"
- message["From"] = "test@example.com"
- message["To"] = "to@example.com, foo@example.com"
-
- # Message body
- part = MIMEText("test file attached")
- message.attach(part)
-
- # Attachment
- part = MIMEText("contents of test file here")
- part.add_header("Content-Disposition", "attachment; filename=test.txt")
- message.attach(part)
+ message = get_raw_email()
kwargs = dict(Source=message["From"], RawMessage={"Data": message.as_string()})
@@ -165,6 +155,39 @@ def test_send_raw_email():
sent_count.should.equal(2)
+@mock_ses
+def test_send_raw_email_validate_domain():
+ conn = boto3.client("ses", region_name="us-east-1")
+
+ message = get_raw_email()
+
+ kwargs = dict(Source=message["From"], RawMessage={"Data": message.as_string()})
+
+ conn.send_raw_email.when.called_with(**kwargs).should.throw(ClientError)
+
+ conn.verify_domain_identity(Domain="example.com")
+ conn.send_raw_email(**kwargs)
+
+ send_quota = conn.get_send_quota()
+ sent_count = int(send_quota["SentLast24Hours"])
+ sent_count.should.equal(2)
+
+
+def get_raw_email():
+ message = MIMEMultipart()
+ message["Subject"] = "Test"
+ message["From"] = "test@example.com"
+ message["To"] = "to@example.com, foo@example.com"
+ # Message body
+ part = MIMEText("test file attached")
+ message.attach(part)
+ # Attachment
+ part = MIMEText("contents of test file here")
+ part.add_header("Content-Disposition", "attachment; filename=test.txt")
+ message.attach(part)
+ return message
+
+
@mock_ses
def test_send_raw_email_without_source():
conn = boto3.client("ses", region_name="us-east-1")
@@ -227,3 +250,94 @@ def test_send_email_notification_with_encoded_sender():
Message={"Subject": {"Data": "hi",}, "Body": {"Text": {"Data": "there",}}},
)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+
+
+@mock_ses
+def test_create_configuration_set():
+ conn = boto3.client("ses", region_name="us-east-1")
+ conn.create_configuration_set(ConfigurationSet=dict({"Name": "test"}))
+
+ conn.create_configuration_set_event_destination(
+ ConfigurationSetName="test",
+ EventDestination={
+ "Name": "snsEvent",
+ "Enabled": True,
+ "MatchingEventTypes": ["send",],
+ "SNSDestination": {
+ "TopicARN": "arn:aws:sns:us-east-1:123456789012:myTopic"
+ },
+ },
+ )
+
+ with assert_raises(ClientError) as ex:
+ conn.create_configuration_set_event_destination(
+ ConfigurationSetName="failtest",
+ EventDestination={
+ "Name": "snsEvent",
+ "Enabled": True,
+ "MatchingEventTypes": ["send",],
+ "SNSDestination": {
+ "TopicARN": "arn:aws:sns:us-east-1:123456789012:myTopic"
+ },
+ },
+ )
+
+ ex.exception.response["Error"]["Code"].should.equal("ConfigurationSetDoesNotExist")
+
+ with assert_raises(ClientError) as ex:
+ conn.create_configuration_set_event_destination(
+ ConfigurationSetName="test",
+ EventDestination={
+ "Name": "snsEvent",
+ "Enabled": True,
+ "MatchingEventTypes": ["send",],
+ "SNSDestination": {
+ "TopicARN": "arn:aws:sns:us-east-1:123456789012:myTopic"
+ },
+ },
+ )
+
+ ex.exception.response["Error"]["Code"].should.equal("EventDestinationAlreadyExists")
+
+
+@mock_ses
+def test_create_ses_template():
+ conn = boto3.client("ses", region_name="us-east-1")
+
+ conn.create_template(
+ Template={
+ "TemplateName": "MyTemplate",
+ "SubjectPart": "Greetings, {{name}}!",
+ "TextPart": "Dear {{name}},"
+ "\r\nYour favorite animal is {{favoriteanimal}}.",
+ "HtmlPart": "Hello {{name}},"
+ "
Your favorite animal is {{favoriteanimal}}.
",
+ }
+ )
+ with assert_raises(ClientError) as ex:
+ conn.create_template(
+ Template={
+ "TemplateName": "MyTemplate",
+ "SubjectPart": "Greetings, {{name}}!",
+ "TextPart": "Dear {{name}},"
+ "\r\nYour favorite animal is {{favoriteanimal}}.",
+ "HtmlPart": "Hello {{name}},"
+ "
Your favorite animal is {{favoriteanimal}}.
",
+ }
+ )
+
+ ex.exception.response["Error"]["Code"].should.equal("TemplateNameAlreadyExists")
+
+ # get a template which is already added
+ result = conn.get_template(TemplateName="MyTemplate")
+ result["Template"]["TemplateName"].should.equal("MyTemplate")
+ result["Template"]["SubjectPart"].should.equal("Greetings, {{name}}!")
+
+ # get a template which is not present
+ with assert_raises(ClientError) as ex:
+ conn.get_template(TemplateName="MyFakeTemplate")
+
+ ex.exception.response["Error"]["Code"].should.equal("TemplateDoesNotExist")
+
+ result = conn.list_templates()
+ result["TemplatesMetadata"][0]["Name"].should.equal("MyTemplate")
diff --git a/tests/test_ssm/test_ssm_boto3.py b/tests/test_ssm/test_ssm_boto3.py
index 170cd8a3e..837f81bf5 100644
--- a/tests/test_ssm/test_ssm_boto3.py
+++ b/tests/test_ssm/test_ssm_boto3.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
+import string
+
import boto3
import botocore.exceptions
import sure # noqa
@@ -300,6 +302,30 @@ def test_get_parameter():
)
+@mock_ssm
+def test_get_parameters_errors():
+ client = boto3.client("ssm", region_name="us-east-1")
+
+ ssm_parameters = {name: "value" for name in string.ascii_lowercase[:11]}
+
+ for name, value in ssm_parameters.items():
+ client.put_parameter(Name=name, Value=value, Type="String")
+
+ with assert_raises(ClientError) as e:
+ client.get_parameters(Names=list(ssm_parameters.keys()))
+ ex = e.exception
+ ex.operation_name.should.equal("GetParameters")
+ ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+ ex.response["Error"]["Code"].should.contain("ValidationException")
+ ex.response["Error"]["Message"].should.equal(
+ "1 validation error detected: "
+ "Value '[{}]' at 'names' failed to satisfy constraint: "
+ "Member must have length less than or equal to 10.".format(
+ ", ".join(ssm_parameters.keys())
+ )
+ )
+
+
@mock_ssm
def test_get_nonexistant_parameter():
client = boto3.client("ssm", region_name="us-east-1")