diff --git a/moto/iot/exceptions.py b/moto/iot/exceptions.py index cd8511b47..23a5da184 100644 --- a/moto/iot/exceptions.py +++ b/moto/iot/exceptions.py @@ -68,3 +68,12 @@ class VersionsLimitExceededException(IoTClientError): "VersionsLimitExceededException", "The policy %s already has the maximum number of versions (5)" % name, ) + + +class ThingStillAttached(IoTClientError): + def __init__(self, name): + self.code = 409 + super().__init__( + "InvalidRequestException", + f"Cannot delete. Thing {name} is still attached to one or more principals", + ) diff --git a/moto/iot/models.py b/moto/iot/models.py index d3eab8a6e..287c71d8c 100644 --- a/moto/iot/models.py +++ b/moto/iot/models.py @@ -22,6 +22,7 @@ from .exceptions import ( VersionConflictException, ResourceAlreadyExistsException, VersionsLimitExceededException, + ThingStillAttached, ) @@ -697,7 +698,7 @@ class IoTBackend(BaseBackend): # detach all principals for k in list(self.principal_things.keys()): if k[1] == thing_name: - del self.principal_things[k] + raise ThingStillAttached(thing_name) del self.things[thing.arn] diff --git a/tests/test_iot/test_iot.py b/tests/test_iot/test_iot.py index deffcd2dc..be2bd3c84 100644 --- a/tests/test_iot/test_iot.py +++ b/tests/test_iot/test_iot.py @@ -1,550 +1,11 @@ -import json import sure # noqa # pylint: disable=unused-import import boto3 -from moto import mock_iot, mock_cognitoidentity -from moto.core import ACCOUNT_ID +from moto import mock_iot from botocore.exceptions import ClientError import pytest -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") - policy_name = "my-policy" - doc = "{}" - - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert["certificateArn"] - client.create_policy(policyName=policy_name, policyDocument=doc) - client.attach_policy(policyName=policy_name, target=cert_arn) - - res = client.list_attached_policies(target=cert_arn) - res.should.have.key("policies").which.should.have.length_of(1) - res["policies"][0]["policyName"].should.equal("my-policy") - - -@mock_iot -@mock_cognitoidentity -def test_attach_policy_to_identity(): - region = "ap-northeast-1" - - cognito_identity_client = boto3.client("cognito-identity", region_name=region) - identity_pool_name = "test_identity_pool" - identity_pool = cognito_identity_client.create_identity_pool( - IdentityPoolName=identity_pool_name, AllowUnauthenticatedIdentities=True - ) - identity = cognito_identity_client.get_id( - AccountId="test", IdentityPoolId=identity_pool["IdentityPoolId"] - ) - - client = boto3.client("iot", region_name=region) - policy_name = "my-policy" - doc = "{}" - client.create_policy(policyName=policy_name, policyDocument=doc) - client.attach_policy(policyName=policy_name, target=identity["IdentityId"]) - - res = client.list_attached_policies(target=identity["IdentityId"]) - res.should.have.key("policies").which.should.have.length_of(1) - res["policies"][0]["policyName"].should.equal(policy_name) - - -@mock_iot -def test_detach_policy(): - client = boto3.client("iot", region_name="ap-northeast-1") - policy_name = "my-policy" - doc = "{}" - - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert["certificateArn"] - client.create_policy(policyName=policy_name, policyDocument=doc) - client.attach_policy(policyName=policy_name, target=cert_arn) - - res = client.list_attached_policies(target=cert_arn) - res.should.have.key("policies").which.should.have.length_of(1) - res["policies"][0]["policyName"].should.equal("my-policy") - - client.detach_policy(policyName=policy_name, target=cert_arn) - res = client.list_attached_policies(target=cert_arn) - res.should.have.key("policies").which.should.be.empty - - -@mock_iot -def test_list_attached_policies(): - client = boto3.client("iot", region_name="ap-northeast-1") - cert = client.create_keys_and_certificate(setAsActive=True) - policies = client.list_attached_policies(target=cert["certificateArn"]) - policies["policies"].should.be.empty - - -@mock_iot -def test_policy_versions(): - client = boto3.client("iot", region_name="ap-northeast-1") - policy_name = "my-policy" - doc = "{}" - - policy = client.create_policy(policyName=policy_name, policyDocument=doc) - policy.should.have.key("policyName").which.should.equal(policy_name) - policy.should.have.key("policyArn").which.should_not.be.none - policy.should.have.key("policyDocument").which.should.equal(json.dumps({})) - policy.should.have.key("policyVersionId").which.should.equal("1") - - policy = client.get_policy(policyName=policy_name) - policy.should.have.key("policyName").which.should.equal(policy_name) - policy.should.have.key("policyArn").which.should_not.be.none - policy.should.have.key("policyDocument").which.should.equal(json.dumps({})) - policy.should.have.key("defaultVersionId").which.should.equal( - policy["defaultVersionId"] - ) - - policy1 = client.create_policy_version( - policyName=policy_name, - policyDocument=json.dumps({"version": "version_1"}), - setAsDefault=True, - ) - policy1.should.have.key("policyArn").which.should_not.be.none - policy1.should.have.key("policyDocument").which.should.equal( - json.dumps({"version": "version_1"}) - ) - policy1.should.have.key("policyVersionId").which.should.equal("2") - policy1.should.have.key("isDefaultVersion").which.should.equal(True) - - policy2 = client.create_policy_version( - policyName=policy_name, - policyDocument=json.dumps({"version": "version_2"}), - setAsDefault=False, - ) - policy2.should.have.key("policyArn").which.should_not.be.none - policy2.should.have.key("policyDocument").which.should.equal( - json.dumps({"version": "version_2"}) - ) - policy2.should.have.key("policyVersionId").which.should.equal("3") - policy2.should.have.key("isDefaultVersion").which.should.equal(False) - - policy = client.get_policy(policyName=policy_name) - policy.should.have.key("policyName").which.should.equal(policy_name) - policy.should.have.key("policyArn").which.should_not.be.none - policy.should.have.key("policyDocument").which.should.equal( - json.dumps({"version": "version_1"}) - ) - policy.should.have.key("defaultVersionId").which.should.equal( - policy1["policyVersionId"] - ) - - policy3 = client.create_policy_version( - policyName=policy_name, - policyDocument=json.dumps({"version": "version_3"}), - setAsDefault=False, - ) - policy3.should.have.key("policyArn").which.should_not.be.none - policy3.should.have.key("policyDocument").which.should.equal( - json.dumps({"version": "version_3"}) - ) - policy3.should.have.key("policyVersionId").which.should.equal("4") - policy3.should.have.key("isDefaultVersion").which.should.equal(False) - - policy4 = client.create_policy_version( - policyName=policy_name, - policyDocument=json.dumps({"version": "version_4"}), - setAsDefault=False, - ) - policy4.should.have.key("policyArn").which.should_not.be.none - policy4.should.have.key("policyDocument").which.should.equal( - json.dumps({"version": "version_4"}) - ) - policy4.should.have.key("policyVersionId").which.should.equal("5") - policy4.should.have.key("isDefaultVersion").which.should.equal(False) - - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key("policyVersions").which.should.have.length_of(5) - list( - map(lambda item: item["isDefaultVersion"], policy_versions["policyVersions"]) - ).count(True).should.equal(1) - default_policy = list( - filter(lambda item: item["isDefaultVersion"], policy_versions["policyVersions"]) - ) - default_policy[0].should.have.key("versionId").should.equal( - policy1["policyVersionId"] - ) - - policy = client.get_policy(policyName=policy_name) - policy.should.have.key("policyName").which.should.equal(policy_name) - policy.should.have.key("policyArn").which.should_not.be.none - policy.should.have.key("policyDocument").which.should.equal( - json.dumps({"version": "version_1"}) - ) - policy.should.have.key("defaultVersionId").which.should.equal( - policy1["policyVersionId"] - ) - - client.set_default_policy_version( - policyName=policy_name, policyVersionId=policy4["policyVersionId"] - ) - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key("policyVersions").which.should.have.length_of(5) - list( - map(lambda item: item["isDefaultVersion"], policy_versions["policyVersions"]) - ).count(True).should.equal(1) - default_policy = list( - filter(lambda item: item["isDefaultVersion"], policy_versions["policyVersions"]) - ) - default_policy[0].should.have.key("versionId").should.equal( - policy4["policyVersionId"] - ) - - policy = client.get_policy(policyName=policy_name) - policy.should.have.key("policyName").which.should.equal(policy_name) - policy.should.have.key("policyArn").which.should_not.be.none - policy.should.have.key("policyDocument").which.should.equal( - json.dumps({"version": "version_4"}) - ) - policy.should.have.key("defaultVersionId").which.should.equal( - policy4["policyVersionId"] - ) - - try: - client.create_policy_version( - policyName=policy_name, - policyDocument=json.dumps({"version": "version_5"}), - setAsDefault=False, - ) - assert False, "Should have failed in previous call" - except Exception as exception: - exception.response["Error"]["Message"].should.equal( - "The policy %s already has the maximum number of versions (5)" % policy_name - ) - - client.delete_policy_version(policyName=policy_name, policyVersionId="1") - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key("policyVersions").which.should.have.length_of(4) - - client.delete_policy_version( - policyName=policy_name, policyVersionId=policy1["policyVersionId"] - ) - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key("policyVersions").which.should.have.length_of(3) - client.delete_policy_version( - policyName=policy_name, policyVersionId=policy2["policyVersionId"] - ) - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key("policyVersions").which.should.have.length_of(2) - - client.delete_policy_version( - policyName=policy_name, policyVersionId=policy3["policyVersionId"] - ) - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key("policyVersions").which.should.have.length_of(1) - - # should fail as it"s the default policy. Should use delete_policy instead - try: - client.delete_policy_version( - policyName=policy_name, policyVersionId=policy4["policyVersionId"] - ) - assert False, "Should have failed in previous call" - except Exception as exception: - exception.response["Error"]["Message"].should.equal( - "Cannot delete the default version of a policy" - ) - - -@mock_iot -def test_things(): - client = boto3.client("iot", region_name="ap-northeast-1") - name = "my-thing" - type_name = "my-type-name" - - # thing type - thing_type = client.create_thing_type(thingTypeName=type_name) - thing_type.should.have.key("thingTypeName").which.should.equal(type_name) - thing_type.should.have.key("thingTypeArn") - thing_type["thingTypeArn"].should.contain(type_name) - - res = client.list_thing_types() - res.should.have.key("thingTypes").which.should.have.length_of(1) - for thing_type in res["thingTypes"]: - thing_type.should.have.key("thingTypeName").which.should_not.be.none - - thing_type = client.describe_thing_type(thingTypeName=type_name) - thing_type.should.have.key("thingTypeName").which.should.equal(type_name) - thing_type.should.have.key("thingTypeProperties") - thing_type.should.have.key("thingTypeMetadata") - thing_type.should.have.key("thingTypeArn") - thing_type["thingTypeArn"].should.contain(type_name) - - # thing - thing = client.create_thing(thingName=name, thingTypeName=type_name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - res = client.list_things() - res.should.have.key("things").which.should.have.length_of(1) - for thing in res["things"]: - thing.should.have.key("thingName").which.should_not.be.none - thing.should.have.key("thingArn").which.should_not.be.none - - thing = client.update_thing( - thingName=name, attributePayload={"attributes": {"k1": "v1"}} - ) - res = client.list_things() - res.should.have.key("things").which.should.have.length_of(1) - for thing in res["things"]: - thing.should.have.key("thingName").which.should_not.be.none - thing.should.have.key("thingArn").which.should_not.be.none - res["things"][0]["attributes"].should.have.key("k1").which.should.equal("v1") - - thing = client.describe_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("defaultClientId") - thing.should.have.key("thingTypeName") - thing.should.have.key("attributes") - thing.should.have.key("version") - - # delete thing - client.delete_thing(thingName=name) - res = client.list_things() - res.should.have.key("things").which.should.have.length_of(0) - - # delete thing type - client.delete_thing_type(thingTypeName=type_name) - res = client.list_thing_types() - res.should.have.key("thingTypes").which.should.have.length_of(0) - - -@mock_iot -def test_list_thing_types(): - client = boto3.client("iot", region_name="ap-northeast-1") - - for i in range(0, 100): - client.create_thing_type(thingTypeName=str(i + 1)) - - thing_types = client.list_thing_types() - thing_types.should.have.key("nextToken") - thing_types.should.have.key("thingTypes").which.should.have.length_of(50) - thing_types["thingTypes"][0]["thingTypeName"].should.equal("1") - thing_types["thingTypes"][-1]["thingTypeName"].should.equal("50") - - thing_types = client.list_thing_types(nextToken=thing_types["nextToken"]) - thing_types.should.have.key("thingTypes").which.should.have.length_of(50) - thing_types.should_not.have.key("nextToken") - thing_types["thingTypes"][0]["thingTypeName"].should.equal("51") - thing_types["thingTypes"][-1]["thingTypeName"].should.equal("100") - - -@mock_iot -def test_list_thing_types_with_typename_filter(): - client = boto3.client("iot", region_name="ap-northeast-1") - - client.create_thing_type(thingTypeName="thing") - client.create_thing_type(thingTypeName="thingType") - client.create_thing_type(thingTypeName="thingTypeName") - client.create_thing_type(thingTypeName="thingTypeNameGroup") - client.create_thing_type(thingTypeName="shouldNotFind") - client.create_thing_type(thingTypeName="find me it shall not") - - thing_types = client.list_thing_types(thingTypeName="thing") - thing_types.should_not.have.key("nextToken") - thing_types.should.have.key("thingTypes").which.should.have.length_of(4) - thing_types["thingTypes"][0]["thingTypeName"].should.equal("thing") - thing_types["thingTypes"][-1]["thingTypeName"].should.equal("thingTypeNameGroup") - - thing_types = client.list_thing_types(thingTypeName="thingTypeName") - thing_types.should_not.have.key("nextToken") - thing_types.should.have.key("thingTypes").which.should.have.length_of(2) - thing_types["thingTypes"][0]["thingTypeName"].should.equal("thingTypeName") - thing_types["thingTypes"][-1]["thingTypeName"].should.equal("thingTypeNameGroup") - - -@mock_iot -def test_list_things_with_next_token(): - client = boto3.client("iot", region_name="ap-northeast-1") - - for i in range(0, 200): - client.create_thing(thingName=str(i + 1)) - - things = client.list_things() - things.should.have.key("nextToken") - things.should.have.key("things").which.should.have.length_of(50) - things["things"][0]["thingName"].should.equal("1") - things["things"][0]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/1" - ) - things["things"][-1]["thingName"].should.equal("50") - things["things"][-1]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/50" - ) - - things = client.list_things(nextToken=things["nextToken"]) - things.should.have.key("nextToken") - things.should.have.key("things").which.should.have.length_of(50) - things["things"][0]["thingName"].should.equal("51") - things["things"][0]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/51" - ) - things["things"][-1]["thingName"].should.equal("100") - things["things"][-1]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/100" - ) - - things = client.list_things(nextToken=things["nextToken"]) - things.should.have.key("nextToken") - things.should.have.key("things").which.should.have.length_of(50) - things["things"][0]["thingName"].should.equal("101") - things["things"][0]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/101" - ) - things["things"][-1]["thingName"].should.equal("150") - things["things"][-1]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/150" - ) - - things = client.list_things(nextToken=things["nextToken"]) - things.should_not.have.key("nextToken") - things.should.have.key("things").which.should.have.length_of(50) - things["things"][0]["thingName"].should.equal("151") - things["things"][0]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/151" - ) - things["things"][-1]["thingName"].should.equal("200") - things["things"][-1]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/200" - ) - - -@mock_iot -def test_list_things_with_attribute_and_thing_type_filter_and_next_token(): - client = boto3.client("iot", region_name="ap-northeast-1") - client.create_thing_type(thingTypeName="my-thing-type") - - for i in range(0, 200): - if not (i + 1) % 3: - attribute_payload = {"attributes": {"foo": "bar"}} - elif not (i + 1) % 5: - attribute_payload = {"attributes": {"bar": "foo"}} - else: - attribute_payload = {} - - if not (i + 1) % 2: - thing_type_name = "my-thing-type" - client.create_thing( - thingName=str(i + 1), - thingTypeName=thing_type_name, - attributePayload=attribute_payload, - ) - else: - client.create_thing( - thingName=str(i + 1), attributePayload=attribute_payload - ) - - # Test filter for thingTypeName - things = client.list_things(thingTypeName=thing_type_name) - things.should.have.key("nextToken") - things.should.have.key("things").which.should.have.length_of(50) - things["things"][0]["thingName"].should.equal("2") - things["things"][0]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/2" - ) - things["things"][-1]["thingName"].should.equal("100") - things["things"][-1]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/100" - ) - all(item["thingTypeName"] == thing_type_name for item in things["things"]) - - things = client.list_things( - nextToken=things["nextToken"], thingTypeName=thing_type_name - ) - things.should_not.have.key("nextToken") - things.should.have.key("things").which.should.have.length_of(50) - things["things"][0]["thingName"].should.equal("102") - things["things"][0]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/102" - ) - things["things"][-1]["thingName"].should.equal("200") - things["things"][-1]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/200" - ) - all(item["thingTypeName"] == thing_type_name for item in things["things"]) - - # Test filter for attributes - things = client.list_things(attributeName="foo", attributeValue="bar") - things.should.have.key("nextToken") - things.should.have.key("things").which.should.have.length_of(50) - things["things"][0]["thingName"].should.equal("3") - things["things"][0]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/3" - ) - things["things"][-1]["thingName"].should.equal("150") - things["things"][-1]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/150" - ) - all(item["attributes"] == {"foo": "bar"} for item in things["things"]) - - things = client.list_things( - nextToken=things["nextToken"], attributeName="foo", attributeValue="bar" - ) - things.should_not.have.key("nextToken") - things.should.have.key("things").which.should.have.length_of(16) - things["things"][0]["thingName"].should.equal("153") - things["things"][0]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/153" - ) - things["things"][-1]["thingName"].should.equal("198") - things["things"][-1]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/198" - ) - all(item["attributes"] == {"foo": "bar"} for item in things["things"]) - - # Test filter for attributes and thingTypeName - things = client.list_things( - thingTypeName=thing_type_name, attributeName="foo", attributeValue="bar" - ) - things.should_not.have.key("nextToken") - things.should.have.key("things").which.should.have.length_of(33) - things["things"][0]["thingName"].should.equal("6") - things["things"][0]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/6" - ) - things["things"][-1]["thingName"].should.equal("198") - things["things"][-1]["thingArn"].should.equal( - f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/198" - ) - all( - item["attributes"] == {"foo": "bar"} - and item["thingTypeName"] == thing_type_name - for item in things["things"] - ) - - @mock_iot def test_endpoints(): region_name = "ap-northeast-1" @@ -585,269 +46,6 @@ def test_endpoints(): raise Exception("Should have raised error") -@mock_iot -def test_certificate_id_generation_deterministic(): - # Creating the same certificate twice should result in the same certificate ID - client = boto3.client("iot", region_name="us-east-1") - cert1 = client.create_keys_and_certificate(setAsActive=False) - client.delete_certificate(certificateId=cert1["certificateId"]) - - cert2 = client.register_certificate( - certificatePem=cert1["certificatePem"], setAsActive=False - ) - cert2.should.have.key("certificateId").which.should.equal(cert1["certificateId"]) - client.delete_certificate(certificateId=cert2["certificateId"]) - - -@mock_iot -def test_certs(): - client = boto3.client("iot", region_name="us-east-1") - cert = client.create_keys_and_certificate(setAsActive=True) - cert.should.have.key("certificateArn").which.should_not.be.none - cert.should.have.key("certificateId").which.should_not.be.none - cert.should.have.key("certificatePem").which.should_not.be.none - cert.should.have.key("keyPair") - cert["keyPair"].should.have.key("PublicKey").which.should_not.be.none - cert["keyPair"].should.have.key("PrivateKey").which.should_not.be.none - cert_id = cert["certificateId"] - - cert = client.describe_certificate(certificateId=cert_id) - cert.should.have.key("certificateDescription") - cert_desc = cert["certificateDescription"] - cert_desc.should.have.key("certificateArn").which.should_not.be.none - cert_desc.should.have.key("certificateId").which.should_not.be.none - cert_desc.should.have.key("certificatePem").which.should_not.be.none - cert_desc.should.have.key("validity").which.should_not.be.none - validity = cert_desc["validity"] - validity.should.have.key("notBefore").which.should_not.be.none - validity.should.have.key("notAfter").which.should_not.be.none - cert_desc.should.have.key("status").which.should.equal("ACTIVE") - cert_pem = cert_desc["certificatePem"] - - res = client.list_certificates() - for cert in res["certificates"]: - cert.should.have.key("certificateArn").which.should_not.be.none - cert.should.have.key("certificateId").which.should_not.be.none - cert.should.have.key("status").which.should_not.be.none - cert.should.have.key("creationDate").which.should_not.be.none - - client.update_certificate(certificateId=cert_id, newStatus="REVOKED") - cert = client.describe_certificate(certificateId=cert_id) - cert_desc = cert["certificateDescription"] - cert_desc.should.have.key("status").which.should.equal("REVOKED") - - client.delete_certificate(certificateId=cert_id) - res = client.list_certificates() - res.should.have.key("certificates") - - # Test register_certificate flow - cert = client.register_certificate(certificatePem=cert_pem, setAsActive=True) - cert.should.have.key("certificateId").which.should_not.be.none - cert.should.have.key("certificateArn").which.should_not.be.none - cert_id = cert["certificateId"] - - res = client.list_certificates() - res.should.have.key("certificates").which.should.have.length_of(1) - for cert in res["certificates"]: - cert.should.have.key("certificateArn").which.should_not.be.none - cert.should.have.key("certificateId").which.should_not.be.none - cert.should.have.key("status").which.should_not.be.none - cert.should.have.key("creationDate").which.should_not.be.none - - client.update_certificate(certificateId=cert_id, newStatus="REVOKED") - cert = client.describe_certificate(certificateId=cert_id) - cert_desc = cert["certificateDescription"] - cert_desc.should.have.key("status").which.should.equal("REVOKED") - - client.delete_certificate(certificateId=cert_id) - res = client.list_certificates() - res.should.have.key("certificates") - - # Test register_certificate without CA flow - cert = client.register_certificate_without_ca( - certificatePem=cert_pem, status="INACTIVE" - ) - cert.should.have.key("certificateId").which.should_not.be.none - cert.should.have.key("certificateArn").which.should_not.be.none - cert_id = cert["certificateId"] - - res = client.list_certificates() - res.should.have.key("certificates").which.should.have.length_of(1) - for cert in res["certificates"]: - cert.should.have.key("certificateArn").which.should_not.be.none - cert.should.have.key("certificateId").which.should_not.be.none - cert.should.have.key("status").which.should_not.be.none - cert.should.have.key("creationDate").which.should_not.be.none - - client.delete_certificate(certificateId=cert_id) - res = client.list_certificates() - res.should.have.key("certificates") - - -@mock_iot -def test_create_certificate_validation(): - # Test we can't create a cert that already exists - client = boto3.client("iot", region_name="us-east-1") - cert = client.create_keys_and_certificate(setAsActive=False) - - with pytest.raises(ClientError) as e: - client.register_certificate( - certificatePem=cert["certificatePem"], setAsActive=False - ) - e.value.response["Error"]["Message"].should.contain( - "The certificate is already provisioned or registered" - ) - - with pytest.raises(ClientError) as e: - client.register_certificate_without_ca( - certificatePem=cert["certificatePem"], status="ACTIVE" - ) - e.value.response["Error"]["Message"].should.contain( - "The certificate is already provisioned or registered" - ) - - -@mock_iot -def test_delete_policy_validation(): - doc = """{ - "Version": "2012-10-17", - "Statement":[ - { - "Effect":"Allow", - "Action":[ - "iot: *" - ], - "Resource":"*" - } - ] - } - """ - client = boto3.client("iot", region_name="ap-northeast-1") - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert["certificateArn"] - policy_name = "my-policy" - client.create_policy(policyName=policy_name, policyDocument=doc) - client.attach_principal_policy(policyName=policy_name, principal=cert_arn) - - with pytest.raises(ClientError) as e: - client.delete_policy(policyName=policy_name) - e.value.response["Error"]["Message"].should.contain( - "The policy cannot be deleted as the policy is attached to one or more principals (name=%s)" - % policy_name - ) - res = client.list_policies() - res.should.have.key("policies").which.should.have.length_of(1) - - client.detach_principal_policy(policyName=policy_name, principal=cert_arn) - client.delete_policy(policyName=policy_name) - res = client.list_policies() - res.should.have.key("policies").which.should.have.length_of(0) - - -@mock_iot -def test_delete_certificate_validation(): - doc = """{ - "Version": "2012-10-17", - "Statement":[ - { - "Effect":"Allow", - "Action":[ - "iot: *" - ], - "Resource":"*" - } - ] - } - """ - client = boto3.client("iot", region_name="ap-northeast-1") - cert = client.create_keys_and_certificate(setAsActive=True) - cert_id = cert["certificateId"] - cert_arn = cert["certificateArn"] - policy_name = "my-policy" - thing_name = "thing-1" - client.create_policy(policyName=policy_name, policyDocument=doc) - client.attach_principal_policy(policyName=policy_name, principal=cert_arn) - client.create_thing(thingName=thing_name) - client.attach_thing_principal(thingName=thing_name, principal=cert_arn) - - with pytest.raises(ClientError) as e: - client.delete_certificate(certificateId=cert_id) - e.value.response["Error"]["Message"].should.contain( - "Certificate must be deactivated (not ACTIVE) before deletion." - ) - res = client.list_certificates() - res.should.have.key("certificates").which.should.have.length_of(1) - - client.update_certificate(certificateId=cert_id, newStatus="REVOKED") - with pytest.raises(ClientError) as e: - client.delete_certificate(certificateId=cert_id) - e.value.response["Error"]["Message"].should.contain( - "Things must be detached before deletion (arn: %s)" % cert_arn - ) - res = client.list_certificates() - res.should.have.key("certificates").which.should.have.length_of(1) - - client.detach_thing_principal(thingName=thing_name, principal=cert_arn) - with pytest.raises(ClientError) as e: - client.delete_certificate(certificateId=cert_id) - e.value.response["Error"]["Message"].should.contain( - "Certificate policies must be detached before deletion (arn: %s)" % cert_arn - ) - res = client.list_certificates() - res.should.have.key("certificates").which.should.have.length_of(1) - - client.detach_principal_policy(policyName=policy_name, principal=cert_arn) - client.delete_certificate(certificateId=cert_id) - res = client.list_certificates() - res.should.have.key("certificates").which.should.have.length_of(0) - - -@mock_iot -def test_certs_create_inactive(): - client = boto3.client("iot", region_name="ap-northeast-1") - cert = client.create_keys_and_certificate(setAsActive=False) - cert_id = cert["certificateId"] - - cert = client.describe_certificate(certificateId=cert_id) - cert.should.have.key("certificateDescription") - cert_desc = cert["certificateDescription"] - cert_desc.should.have.key("status").which.should.equal("INACTIVE") - - client.update_certificate(certificateId=cert_id, newStatus="ACTIVE") - cert = client.describe_certificate(certificateId=cert_id) - cert.should.have.key("certificateDescription") - cert_desc = cert["certificateDescription"] - cert_desc.should.have.key("status").which.should.equal("ACTIVE") - - -@mock_iot -def test_policy(): - client = boto3.client("iot", region_name="ap-northeast-1") - name = "my-policy" - doc = "{}" - policy = client.create_policy(policyName=name, policyDocument=doc) - policy.should.have.key("policyName").which.should.equal(name) - policy.should.have.key("policyArn").which.should_not.be.none - policy.should.have.key("policyDocument").which.should.equal(doc) - policy.should.have.key("policyVersionId").which.should.equal("1") - - policy = client.get_policy(policyName=name) - policy.should.have.key("policyName").which.should.equal(name) - policy.should.have.key("policyArn").which.should_not.be.none - policy.should.have.key("policyDocument").which.should.equal(doc) - policy.should.have.key("defaultVersionId").which.should.equal("1") - - res = client.list_policies() - res.should.have.key("policies").which.should.have.length_of(1) - for policy in res["policies"]: - policy.should.have.key("policyName").which.should_not.be.none - policy.should.have.key("policyArn").which.should_not.be.none - - client.delete_policy(policyName=name) - res = client.list_policies() - res.should.have.key("policies").which.should.have.length_of(0) - - @mock_iot def test_principal_policy(): client = boto3.client("iot", region_name="ap-northeast-1") @@ -949,1619 +147,3 @@ def test_principal_thing(): e.value.response["Error"]["Message"].should.equal( "Failed to list principals for thing xxx because the thing does not exist in your account" ) - - -@mock_iot -def test_delete_principal_thing(): - client = boto3.client("iot", region_name="ap-northeast-1") - thing_name = "my-thing" - client.create_thing(thingName=thing_name) - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert["certificateArn"] - cert_id = cert["certificateId"] - - client.attach_thing_principal(thingName=thing_name, principal=cert_arn) - - client.delete_thing(thingName=thing_name) - res = client.list_principal_things(principal=cert_arn) - res.should.have.key("things").which.should.have.length_of(0) - - client.update_certificate(certificateId=cert_id, newStatus="INACTIVE") - 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") - 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") - 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") - 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 pytest.raises(ClientError) as e: - client.list_thing_groups(parentGroup="inexistant-group-name") - e.value.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") - 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") - 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") - 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") - 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" - tree_dict = { - group_name_1a: {group_name_2a: {},}, - } - generate_thing_group_tree(client, tree_dict) - - # delete group with child - try: - client.delete_thing_group(thingGroupName=group_name_1a) - except client.exceptions.InvalidRequestException as exc: - error_code = exc.response["Error"]["Code"] - error_code.should.equal("InvalidRequestException") - else: - raise Exception("Should have raised error") - - # delete child group - client.delete_thing_group(thingGroupName=group_name_2a) - res = client.list_thing_groups() - res.should.have.key("thingGroups").which.should.have.length_of(1) - res["thingGroups"].should_not.have.key(group_name_2a) - - # now that there is no child group, we can delete the previous group safely - client.delete_thing_group(thingGroupName=group_name_1a) - res = client.list_thing_groups() - res.should.have.key("thingGroups").which.should.have.length_of(0) - - # Deleting an invalid thing group does not raise an error. - res = client.delete_thing_group(thingGroupName="non-existent-group-name") - res["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) - - -@mock_iot -def test_describe_thing_group_metadata_hierarchy(): - client = boto3.client("iot", region_name="ap-northeast-1") - 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: {}, - } - group_catalog = generate_thing_group_tree(client, tree_dict) - - # describe groups - # groups level 1 - # 1a - thing_group_description1a = client.describe_thing_group( - thingGroupName=group_name_1a - ) - thing_group_description1a.should.have.key("thingGroupName").which.should.equal( - group_name_1a - ) - thing_group_description1a.should.have.key("thingGroupProperties") - thing_group_description1a.should.have.key("thingGroupMetadata") - thing_group_description1a["thingGroupMetadata"].should.have.key("creationDate") - thing_group_description1a.should.have.key("version") - # 1b - thing_group_description1b = client.describe_thing_group( - thingGroupName=group_name_1b - ) - thing_group_description1b.should.have.key("thingGroupName").which.should.equal( - group_name_1b - ) - thing_group_description1b.should.have.key("thingGroupProperties") - thing_group_description1b.should.have.key("thingGroupMetadata") - thing_group_description1b["thingGroupMetadata"].should.have.length_of(1) - thing_group_description1b["thingGroupMetadata"].should.have.key("creationDate") - thing_group_description1b.should.have.key("version") - # groups level 2 - # 2a - thing_group_description2a = client.describe_thing_group( - thingGroupName=group_name_2a - ) - thing_group_description2a.should.have.key("thingGroupName").which.should.equal( - group_name_2a - ) - thing_group_description2a.should.have.key("thingGroupProperties") - thing_group_description2a.should.have.key("thingGroupMetadata") - thing_group_description2a["thingGroupMetadata"].should.have.length_of(3) - thing_group_description2a["thingGroupMetadata"].should.have.key( - "parentGroupName" - ).being.equal(group_name_1a) - thing_group_description2a["thingGroupMetadata"].should.have.key( - "rootToParentThingGroups" - ) - thing_group_description2a["thingGroupMetadata"][ - "rootToParentThingGroups" - ].should.have.length_of(1) - thing_group_description2a["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupName" - ].should.match(group_name_1a) - thing_group_description2a["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupArn" - ].should.match(group_catalog[group_name_1a]["thingGroupArn"]) - thing_group_description2a.should.have.key("version") - # 2b - thing_group_description2b = client.describe_thing_group( - thingGroupName=group_name_2b - ) - thing_group_description2b.should.have.key("thingGroupName").which.should.equal( - group_name_2b - ) - thing_group_description2b.should.have.key("thingGroupProperties") - thing_group_description2b.should.have.key("thingGroupMetadata") - thing_group_description2b["thingGroupMetadata"].should.have.length_of(3) - thing_group_description2b["thingGroupMetadata"].should.have.key( - "parentGroupName" - ).being.equal(group_name_1a) - thing_group_description2b["thingGroupMetadata"].should.have.key( - "rootToParentThingGroups" - ) - thing_group_description2b["thingGroupMetadata"][ - "rootToParentThingGroups" - ].should.have.length_of(1) - thing_group_description2b["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupName" - ].should.match(group_name_1a) - thing_group_description2b["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupArn" - ].should.match(group_catalog[group_name_1a]["thingGroupArn"]) - thing_group_description2b.should.have.key("version") - # groups level 3 - # 3a - thing_group_description3a = client.describe_thing_group( - thingGroupName=group_name_3a - ) - thing_group_description3a.should.have.key("thingGroupName").which.should.equal( - group_name_3a - ) - thing_group_description3a.should.have.key("thingGroupProperties") - thing_group_description3a.should.have.key("thingGroupMetadata") - thing_group_description3a["thingGroupMetadata"].should.have.length_of(3) - thing_group_description3a["thingGroupMetadata"].should.have.key( - "parentGroupName" - ).being.equal(group_name_2a) - thing_group_description3a["thingGroupMetadata"].should.have.key( - "rootToParentThingGroups" - ) - thing_group_description3a["thingGroupMetadata"][ - "rootToParentThingGroups" - ].should.have.length_of(2) - thing_group_description3a["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupName" - ].should.match(group_name_1a) - thing_group_description3a["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupArn" - ].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(group_catalog[group_name_2a]["thingGroupArn"]) - thing_group_description3a.should.have.key("version") - # 3b - thing_group_description3b = client.describe_thing_group( - thingGroupName=group_name_3b - ) - thing_group_description3b.should.have.key("thingGroupName").which.should.equal( - group_name_3b - ) - thing_group_description3b.should.have.key("thingGroupProperties") - thing_group_description3b.should.have.key("thingGroupMetadata") - thing_group_description3b["thingGroupMetadata"].should.have.length_of(3) - thing_group_description3b["thingGroupMetadata"].should.have.key( - "parentGroupName" - ).being.equal(group_name_2a) - thing_group_description3b["thingGroupMetadata"].should.have.key( - "rootToParentThingGroups" - ) - thing_group_description3b["thingGroupMetadata"][ - "rootToParentThingGroups" - ].should.have.length_of(2) - thing_group_description3b["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupName" - ].should.match(group_name_1a) - thing_group_description3b["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupArn" - ].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(group_catalog[group_name_2a]["thingGroupArn"]) - thing_group_description3b.should.have.key("version") - # 3c - thing_group_description3c = client.describe_thing_group( - thingGroupName=group_name_3c - ) - thing_group_description3c.should.have.key("thingGroupName").which.should.equal( - group_name_3c - ) - thing_group_description3c.should.have.key("thingGroupProperties") - thing_group_description3c.should.have.key("thingGroupMetadata") - thing_group_description3c["thingGroupMetadata"].should.have.length_of(3) - thing_group_description3c["thingGroupMetadata"].should.have.key( - "parentGroupName" - ).being.equal(group_name_2b) - thing_group_description3c["thingGroupMetadata"].should.have.key( - "rootToParentThingGroups" - ) - thing_group_description3c["thingGroupMetadata"][ - "rootToParentThingGroups" - ].should.have.length_of(2) - thing_group_description3c["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupName" - ].should.match(group_name_1a) - thing_group_description3c["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupArn" - ].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(group_catalog[group_name_2b]["thingGroupArn"]) - thing_group_description3c.should.have.key("version") - # 3d - thing_group_description3d = client.describe_thing_group( - thingGroupName=group_name_3d - ) - thing_group_description3d.should.have.key("thingGroupName").which.should.equal( - group_name_3d - ) - thing_group_description3d.should.have.key("thingGroupProperties") - thing_group_description3d.should.have.key("thingGroupMetadata") - thing_group_description3d["thingGroupMetadata"].should.have.length_of(3) - thing_group_description3d["thingGroupMetadata"].should.have.key( - "parentGroupName" - ).being.equal(group_name_2b) - thing_group_description3d["thingGroupMetadata"].should.have.key( - "rootToParentThingGroups" - ) - thing_group_description3d["thingGroupMetadata"][ - "rootToParentThingGroups" - ].should.have.length_of(2) - thing_group_description3d["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupName" - ].should.match(group_name_1a) - thing_group_description3d["thingGroupMetadata"]["rootToParentThingGroups"][0][ - "groupArn" - ].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(group_catalog[group_name_2b]["thingGroupArn"]) - thing_group_description3d.should.have.key("version") - - -@mock_iot -def test_thing_groups(): - client = boto3.client("iot", region_name="ap-northeast-1") - group_name = "my-group-name" - - # thing group - thing_group = client.create_thing_group(thingGroupName=group_name) - thing_group.should.have.key("thingGroupName").which.should.equal(group_name) - thing_group.should.have.key("thingGroupArn") - thing_group["thingGroupArn"].should.contain(group_name) - - res = client.list_thing_groups() - res.should.have.key("thingGroups").which.should.have.length_of(1) - for thing_group in res["thingGroups"]: - thing_group.should.have.key("groupName").which.should_not.be.none - thing_group.should.have.key("groupArn").which.should_not.be.none - - thing_group = client.describe_thing_group(thingGroupName=group_name) - thing_group.should.have.key("thingGroupName").which.should.equal(group_name) - thing_group.should.have.key("thingGroupProperties") - thing_group.should.have.key("thingGroupMetadata") - thing_group.should.have.key("version") - thing_group.should.have.key("thingGroupArn") - thing_group["thingGroupArn"].should.contain(group_name) - - # delete thing group - client.delete_thing_group(thingGroupName=group_name) - res = client.list_thing_groups() - res.should.have.key("thingGroups").which.should.have.length_of(0) - - # props create test - props = { - "thingGroupDescription": "my first thing group", - "attributePayload": {"attributes": {"key1": "val01", "Key02": "VAL2"}}, - } - thing_group = client.create_thing_group( - thingGroupName=group_name, thingGroupProperties=props - ) - thing_group.should.have.key("thingGroupName").which.should.equal(group_name) - thing_group.should.have.key("thingGroupArn") - - thing_group = client.describe_thing_group(thingGroupName=group_name) - thing_group.should.have.key("thingGroupProperties").which.should.have.key( - "attributePayload" - ).which.should.have.key("attributes") - res_props = thing_group["thingGroupProperties"]["attributePayload"]["attributes"] - res_props.should.have.key("key1").which.should.equal("val01") - res_props.should.have.key("Key02").which.should.equal("VAL2") - - # props update test with merge - new_props = {"attributePayload": {"attributes": {"k3": "v3"}, "merge": True}} - client.update_thing_group(thingGroupName=group_name, thingGroupProperties=new_props) - thing_group = client.describe_thing_group(thingGroupName=group_name) - thing_group.should.have.key("thingGroupProperties").which.should.have.key( - "attributePayload" - ).which.should.have.key("attributes") - res_props = thing_group["thingGroupProperties"]["attributePayload"]["attributes"] - res_props.should.have.key("key1").which.should.equal("val01") - res_props.should.have.key("Key02").which.should.equal("VAL2") - - res_props.should.have.key("k3").which.should.equal("v3") - - # props update test - new_props = {"attributePayload": {"attributes": {"k4": "v4"}}} - client.update_thing_group(thingGroupName=group_name, thingGroupProperties=new_props) - thing_group = client.describe_thing_group(thingGroupName=group_name) - thing_group.should.have.key("thingGroupProperties").which.should.have.key( - "attributePayload" - ).which.should.have.key("attributes") - res_props = thing_group["thingGroupProperties"]["attributePayload"]["attributes"] - res_props.should.have.key("k4").which.should.equal("v4") - res_props.should_not.have.key("key1") - - -@mock_iot -def test_thing_group_relations(): - client = boto3.client("iot", region_name="ap-northeast-1") - name = "my-thing" - group_name = "my-group-name" - - # thing group - thing_group = client.create_thing_group(thingGroupName=group_name) - thing_group.should.have.key("thingGroupName").which.should.equal(group_name) - thing_group.should.have.key("thingGroupArn") - - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # add in 4 way - client.add_thing_to_thing_group(thingGroupName=group_name, thingName=name) - client.add_thing_to_thing_group( - thingGroupArn=thing_group["thingGroupArn"], thingArn=thing["thingArn"] - ) - client.add_thing_to_thing_group( - thingGroupName=group_name, thingArn=thing["thingArn"] - ) - client.add_thing_to_thing_group( - thingGroupArn=thing_group["thingGroupArn"], thingName=name - ) - - things = client.list_things_in_thing_group(thingGroupName=group_name) - things.should.have.key("things") - things["things"].should.have.length_of(1) - - thing_groups = client.list_thing_groups_for_thing(thingName=name) - thing_groups.should.have.key("thingGroups") - thing_groups["thingGroups"].should.have.length_of(1) - - # remove in 4 way - client.remove_thing_from_thing_group(thingGroupName=group_name, thingName=name) - client.remove_thing_from_thing_group( - thingGroupArn=thing_group["thingGroupArn"], thingArn=thing["thingArn"] - ) - client.remove_thing_from_thing_group( - thingGroupName=group_name, thingArn=thing["thingArn"] - ) - client.remove_thing_from_thing_group( - thingGroupArn=thing_group["thingGroupArn"], thingName=name - ) - things = client.list_things_in_thing_group(thingGroupName=group_name) - things.should.have.key("things") - things["things"].should.have.length_of(0) - - # update thing group for thing - client.update_thing_groups_for_thing(thingName=name, thingGroupsToAdd=[group_name]) - things = client.list_things_in_thing_group(thingGroupName=group_name) - things.should.have.key("things") - things["things"].should.have.length_of(1) - - client.update_thing_groups_for_thing( - thingName=name, thingGroupsToRemove=[group_name] - ) - things = client.list_things_in_thing_group(thingGroupName=group_name) - things.should.have.key("things") - things["things"].should.have.length_of(0) - - -@mock_iot -def test_create_job(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing# job document - # job_document = { - # "field": "value" - # } - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # job document - job_document = {"field": "value"} - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - document=json.dumps(job_document), - description="Description", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - job.should.have.key("description") - - -@mock_iot -def test_list_jobs(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing# job document - # job_document = { - # "field": "value" - # } - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # job document - job_document = {"field": "value"} - - job1 = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - document=json.dumps(job_document), - description="Description", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job1.should.have.key("jobId").which.should.equal(job_id) - job1.should.have.key("jobArn") - job1.should.have.key("description") - - job2 = client.create_job( - jobId=job_id + "1", - targets=[thing["thingArn"]], - document=json.dumps(job_document), - description="Description", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job2.should.have.key("jobId").which.should.equal(job_id + "1") - job2.should.have.key("jobArn") - job2.should.have.key("description") - - jobs = client.list_jobs() - jobs.should.have.key("jobs") - jobs.should_not.have.key("nextToken") - jobs["jobs"][0].should.have.key("jobId").which.should.equal(job_id) - jobs["jobs"][1].should.have.key("jobId").which.should.equal(job_id + "1") - - -@mock_iot -def test_describe_job(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - - job = client.describe_job(jobId=job_id) - job.should.have.key("documentSource") - job.should.have.key("job") - job.should.have.key("job").which.should.have.key("jobArn") - job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("job").which.should.have.key("targets") - job.should.have.key("job").which.should.have.key("jobProcessDetails") - job.should.have.key("job").which.should.have.key("lastUpdatedAt") - job.should.have.key("job").which.should.have.key("createdAt") - job.should.have.key("job").which.should.have.key("jobExecutionsRolloutConfig") - job.should.have.key("job").which.should.have.key( - "targetSelection" - ).which.should.equal("CONTINUOUS") - job.should.have.key("job").which.should.have.key("presignedUrlConfig") - job.should.have.key("job").which.should.have.key( - "presignedUrlConfig" - ).which.should.have.key("roleArn").which.should.equal( - "arn:aws:iam::1:role/service-role/iot_job_role" - ) - job.should.have.key("job").which.should.have.key( - "presignedUrlConfig" - ).which.should.have.key("expiresInSec").which.should.equal(123) - job.should.have.key("job").which.should.have.key( - "jobExecutionsRolloutConfig" - ).which.should.have.key("maximumPerMinute").which.should.equal(10) - - -@mock_iot -def test_describe_job_1(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # job document - job_document = {"field": "value"} - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - document=json.dumps(job_document), - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - - job = client.describe_job(jobId=job_id) - job.should.have.key("job") - job.should.have.key("job").which.should.have.key("jobArn") - job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("job").which.should.have.key("targets") - job.should.have.key("job").which.should.have.key("jobProcessDetails") - job.should.have.key("job").which.should.have.key("lastUpdatedAt") - job.should.have.key("job").which.should.have.key("createdAt") - job.should.have.key("job").which.should.have.key("jobExecutionsRolloutConfig") - job.should.have.key("job").which.should.have.key( - "targetSelection" - ).which.should.equal("CONTINUOUS") - job.should.have.key("job").which.should.have.key("presignedUrlConfig") - job.should.have.key("job").which.should.have.key( - "presignedUrlConfig" - ).which.should.have.key("roleArn").which.should.equal( - "arn:aws:iam::1:role/service-role/iot_job_role" - ) - job.should.have.key("job").which.should.have.key( - "presignedUrlConfig" - ).which.should.have.key("expiresInSec").which.should.equal(123) - job.should.have.key("job").which.should.have.key( - "jobExecutionsRolloutConfig" - ).which.should.have.key("maximumPerMinute").which.should.equal(10) - - -@mock_iot -def test_delete_job(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - - job = client.describe_job(jobId=job_id) - job.should.have.key("job") - job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) - - client.delete_job(jobId=job_id) - - client.list_jobs()["jobs"].should.have.length_of(0) - - -@mock_iot -def test_cancel_job(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - - job = client.describe_job(jobId=job_id) - job.should.have.key("job") - job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) - - job = client.cancel_job(jobId=job_id, reasonCode="Because", comment="You are") - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - - job = client.describe_job(jobId=job_id) - job.should.have.key("job") - job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("job").which.should.have.key("status").which.should.equal( - "CANCELED" - ) - job.should.have.key("job").which.should.have.key( - "forceCanceled" - ).which.should.equal(False) - job.should.have.key("job").which.should.have.key("reasonCode").which.should.equal( - "Because" - ) - job.should.have.key("job").which.should.have.key("comment").which.should.equal( - "You are" - ) - - -@mock_iot -def test_get_job_document_with_document_source(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - - job_document = client.get_job_document(jobId=job_id) - job_document.should.have.key("document").which.should.equal("") - - -@mock_iot -def test_get_job_document_with_document(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # job document - job_document = {"field": "value"} - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - document=json.dumps(job_document), - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - - job_document = client.get_job_document(jobId=job_id) - job_document.should.have.key("document").which.should.equal('{"field": "value"}') - - -@mock_iot -def test_describe_job_execution(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # job document - job_document = {"field": "value"} - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - document=json.dumps(job_document), - description="Description", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - job.should.have.key("description") - - job_execution = client.describe_job_execution(jobId=job_id, thingName=name) - job_execution.should.have.key("execution") - job_execution["execution"].should.have.key("jobId").which.should.equal(job_id) - job_execution["execution"].should.have.key("status").which.should.equal("QUEUED") - job_execution["execution"].should.have.key("forceCanceled").which.should.equal( - False - ) - job_execution["execution"].should.have.key("statusDetails").which.should.equal( - {"detailsMap": {}} - ) - job_execution["execution"].should.have.key("thingArn").which.should.equal( - thing["thingArn"] - ) - job_execution["execution"].should.have.key("queuedAt") - job_execution["execution"].should.have.key("startedAt") - job_execution["execution"].should.have.key("lastUpdatedAt") - job_execution["execution"].should.have.key("executionNumber").which.should.equal( - 123 - ) - job_execution["execution"].should.have.key("versionNumber").which.should.equal(123) - job_execution["execution"].should.have.key( - "approximateSecondsBeforeTimedOut" - ).which.should.equal(123) - - job_execution = client.describe_job_execution( - jobId=job_id, thingName=name, executionNumber=123 - ) - job_execution.should.have.key("execution") - job_execution["execution"].should.have.key("jobId").which.should.equal(job_id) - job_execution["execution"].should.have.key("status").which.should.equal("QUEUED") - job_execution["execution"].should.have.key("forceCanceled").which.should.equal( - False - ) - job_execution["execution"].should.have.key("statusDetails").which.should.equal( - {"detailsMap": {}} - ) - job_execution["execution"].should.have.key("thingArn").which.should.equal( - thing["thingArn"] - ) - job_execution["execution"].should.have.key("queuedAt") - job_execution["execution"].should.have.key("startedAt") - job_execution["execution"].should.have.key("lastUpdatedAt") - job_execution["execution"].should.have.key("executionNumber").which.should.equal( - 123 - ) - job_execution["execution"].should.have.key("versionNumber").which.should.equal(123) - job_execution["execution"].should.have.key( - "approximateSecondsBeforeTimedOut" - ).which.should.equal(123) - - try: - client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=456) - except ClientError as exc: - error_code = exc.response["Error"]["Code"] - error_code.should.equal("ResourceNotFoundException") - else: - raise Exception("Should have raised error") - - -@mock_iot -def test_cancel_job_execution(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # job document - job_document = {"field": "value"} - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - document=json.dumps(job_document), - description="Description", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - job.should.have.key("description") - - client.cancel_job_execution(jobId=job_id, thingName=name) - job_execution = client.describe_job_execution(jobId=job_id, thingName=name) - job_execution.should.have.key("execution") - job_execution["execution"].should.have.key("status").which.should.equal("CANCELED") - - -@mock_iot -def test_delete_job_execution(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # job document - job_document = {"field": "value"} - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - document=json.dumps(job_document), - description="Description", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - job.should.have.key("description") - - client.delete_job_execution(jobId=job_id, thingName=name, executionNumber=123) - try: - client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=123) - except ClientError as exc: - error_code = exc.response["Error"]["Code"] - error_code.should.equal("ResourceNotFoundException") - else: - raise Exception("Should have raised error") - - -@mock_iot -def test_list_job_executions_for_job(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # job document - job_document = {"field": "value"} - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - document=json.dumps(job_document), - description="Description", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - job.should.have.key("description") - - job_execution = client.list_job_executions_for_job(jobId=job_id) - job_execution.should.have.key("executionSummaries") - job_execution["executionSummaries"][0].should.have.key( - "thingArn" - ).which.should.equal(thing["thingArn"]) - - job_execution = client.list_job_executions_for_job(jobId=job_id, status="QUEUED") - job_execution.should.have.key("executionSummaries") - job_execution["executionSummaries"][0].should.have.key( - "thingArn" - ).which.should.equal(thing["thingArn"]) - - -@mock_iot -def test_list_job_executions_for_thing(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - job_id = "TestJob" - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key("thingName").which.should.equal(name) - thing.should.have.key("thingArn") - - # job document - job_document = {"field": "value"} - - job = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - document=json.dumps(job_document), - description="Description", - presignedUrlConfig={ - "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", - "expiresInSec": 123, - }, - targetSelection="CONTINUOUS", - jobExecutionsRolloutConfig={"maximumPerMinute": 10}, - ) - - job.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key("jobArn") - job.should.have.key("description") - - job_execution = client.list_job_executions_for_thing(thingName=name) - job_execution.should.have.key("executionSummaries") - job_execution["executionSummaries"][0].should.have.key("jobId").which.should.equal( - job_id - ) - - job_execution = client.list_job_executions_for_thing( - thingName=name, status="QUEUED" - ) - job_execution.should.have.key("executionSummaries") - job_execution["executionSummaries"][0].should.have.key("jobId").which.should.equal( - job_id - ) - - -@mock_iot -def test_list_job_executions_for_thing_paginated(): - client = boto3.client("iot", region_name="eu-west-1") - name = "my-thing" - thing = client.create_thing(thingName=name) - - for idx in range(0, 10): - client.create_job( - jobId=f"TestJob_{idx}", - targets=[thing["thingArn"]], - document=json.dumps({"field": "value"}), - ) - - res = client.list_job_executions_for_thing(thingName=name, maxResults=2) - executions = res["executionSummaries"] - executions.should.have.length_of(2) - res.should.have.key("nextToken") - - res = client.list_job_executions_for_thing( - thingName=name, maxResults=1, nextToken=res["nextToken"] - ) - executions = res["executionSummaries"] - executions.should.have.length_of(1) - res.should.have.key("nextToken") - - res = client.list_job_executions_for_thing( - thingName=name, nextToken=res["nextToken"] - ) - executions = res["executionSummaries"] - executions.should.have.length_of(7) - res.shouldnt.have.key("nextToken") - - -class TestTopicRules: - name = "my-rule" - payload = { - "sql": "SELECT * FROM 'topic/*' WHERE something > 0", - "actions": [ - {"dynamoDBv2": {"putItem": {"tableName": "my-table"}, "roleArn": "my-role"}} - ], - "errorAction": { - "republish": {"qos": 0, "roleArn": "my-role", "topic": "other-topic"} - }, - "description": "my-description", - "ruleDisabled": False, - "awsIotSqlVersion": "2016-03-23", - } - - @mock_iot - def test_topic_rule_create(self): - client = boto3.client("iot", region_name="ap-northeast-1") - - client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload) - - # duplicated rule name - with pytest.raises(ClientError) as ex: - client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload) - error_code = ex.value.response["Error"]["Code"] - error_code.should.equal("ResourceAlreadyExistsException") - - @mock_iot - def test_topic_rule_list(self): - client = boto3.client("iot", region_name="ap-northeast-1") - - # empty response - res = client.list_topic_rules() - res.should.have.key("rules").which.should.have.length_of(0) - - client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload) - client.create_topic_rule(ruleName="my-rule-2", topicRulePayload=self.payload) - - res = client.list_topic_rules() - res.should.have.key("rules").which.should.have.length_of(2) - for rule, name in zip(res["rules"], [self.name, "my-rule-2"]): - rule.should.have.key("ruleName").which.should.equal(name) - rule.should.have.key("createdAt").which.should_not.be.none - rule.should.have.key("ruleArn").which.should_not.be.none - rule.should.have.key("ruleDisabled").which.should.equal( - self.payload["ruleDisabled"] - ) - rule.should.have.key("topicPattern").which.should.equal("topic/*") - - @mock_iot - def test_topic_rule_get(self): - client = boto3.client("iot", region_name="ap-northeast-1") - - # no such rule - with pytest.raises(ClientError) as ex: - client.get_topic_rule(ruleName=self.name) - error_code = ex.value.response["Error"]["Code"] - error_code.should.equal("ResourceNotFoundException") - - client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload) - - rule = client.get_topic_rule(ruleName=self.name) - - rule.should.have.key("ruleArn").which.should_not.be.none - rule.should.have.key("rule") - rrule = rule["rule"] - rrule.should.have.key("actions").which.should.equal(self.payload["actions"]) - rrule.should.have.key("awsIotSqlVersion").which.should.equal( - self.payload["awsIotSqlVersion"] - ) - rrule.should.have.key("createdAt").which.should_not.be.none - rrule.should.have.key("description").which.should.equal( - self.payload["description"] - ) - rrule.should.have.key("errorAction").which.should.equal( - self.payload["errorAction"] - ) - rrule.should.have.key("ruleDisabled").which.should.equal( - self.payload["ruleDisabled"] - ) - rrule.should.have.key("ruleName").which.should.equal(self.name) - rrule.should.have.key("sql").which.should.equal(self.payload["sql"]) - - @mock_iot - def test_topic_rule_replace(self): - client = boto3.client("iot", region_name="ap-northeast-1") - - # no such rule - with pytest.raises(ClientError) as ex: - client.replace_topic_rule(ruleName=self.name, topicRulePayload=self.payload) - error_code = ex.value.response["Error"]["Code"] - error_code.should.equal("ResourceNotFoundException") - - client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload) - - payload = self.payload.copy() - payload["description"] = "new-description" - client.replace_topic_rule( - ruleName=self.name, topicRulePayload=payload, - ) - - rule = client.get_topic_rule(ruleName=self.name) - rule["rule"]["ruleName"].should.equal(self.name) - rule["rule"]["description"].should.equal(payload["description"]) - - @mock_iot - def test_topic_rule_disable(self): - client = boto3.client("iot", region_name="ap-northeast-1") - - # no such rule - with pytest.raises(ClientError) as ex: - client.disable_topic_rule(ruleName=self.name) - error_code = ex.value.response["Error"]["Code"] - error_code.should.equal("ResourceNotFoundException") - - client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload) - - client.disable_topic_rule(ruleName=self.name) - - rule = client.get_topic_rule(ruleName=self.name) - rule["rule"]["ruleName"].should.equal(self.name) - rule["rule"]["ruleDisabled"].should.equal(True) - - @mock_iot - def test_topic_rule_enable(self): - client = boto3.client("iot", region_name="ap-northeast-1") - - # no such rule - with pytest.raises(ClientError) as ex: - client.enable_topic_rule(ruleName=self.name) - error_code = ex.value.response["Error"]["Code"] - error_code.should.equal("ResourceNotFoundException") - - payload = self.payload.copy() - payload["ruleDisabled"] = True - client.create_topic_rule(ruleName=self.name, topicRulePayload=payload) - - client.enable_topic_rule(ruleName=self.name) - - rule = client.get_topic_rule(ruleName=self.name) - rule["rule"]["ruleName"].should.equal(self.name) - rule["rule"]["ruleDisabled"].should.equal(False) - - @mock_iot - def test_topic_rule_delete(self): - client = boto3.client("iot", region_name="ap-northeast-1") - - # no such rule - with pytest.raises(ClientError) as ex: - client.delete_topic_rule(ruleName=self.name) - error_code = ex.value.response["Error"]["Code"] - error_code.should.equal("ResourceNotFoundException") - - client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload) - - client.enable_topic_rule(ruleName=self.name) - - client.delete_topic_rule(ruleName=self.name) - - res = client.list_topic_rules() - res.should.have.key("rules").which.should.have.length_of(0) - - @mock_iot - def test_deprecate_undeprecate_thing_type(self): - client = boto3.client("iot", region_name="ap-northeast-1") - thing_type_name = "my-type-name" - client.create_thing_type( - thingTypeName=thing_type_name, - thingTypeProperties={"searchableAttributes": ["s1", "s2", "s3"]}, - ) - - res = client.describe_thing_type(thingTypeName=thing_type_name) - res["thingTypeMetadata"]["deprecated"].should.equal(False) - client.deprecate_thing_type(thingTypeName=thing_type_name, undoDeprecate=False) - - res = client.describe_thing_type(thingTypeName=thing_type_name) - res["thingTypeMetadata"]["deprecated"].should.equal(True) - - client.deprecate_thing_type(thingTypeName=thing_type_name, undoDeprecate=True) - - res = client.describe_thing_type(thingTypeName=thing_type_name) - res["thingTypeMetadata"]["deprecated"].should.equal(False) - - @mock_iot - def test_deprecate_thing_type_not_exist(self): - client = boto3.client("iot", region_name="ap-northeast-1") - thing_type_name = "my-type-name" - with pytest.raises(client.exceptions.ResourceNotFoundException): - client.deprecate_thing_type( - thingTypeName=thing_type_name, undoDeprecate=False - ) - - @mock_iot - def test_create_thing_with_deprecated_type(self): - client = boto3.client("iot", region_name="ap-northeast-1") - thing_type_name = "my-type-name" - client.create_thing_type( - thingTypeName=thing_type_name, - thingTypeProperties={"searchableAttributes": ["s1", "s2", "s3"]}, - ) - client.deprecate_thing_type(thingTypeName=thing_type_name, undoDeprecate=False) - with pytest.raises(client.exceptions.InvalidRequestException): - client.create_thing(thingName="thing-name", thingTypeName=thing_type_name) - - @mock_iot - def test_update_thing_with_deprecated_type(self): - client = boto3.client("iot", region_name="ap-northeast-1") - thing_type_name = "my-type-name" - thing_name = "thing-name" - - client.create_thing_type( - thingTypeName=thing_type_name, - thingTypeProperties={"searchableAttributes": ["s1", "s2", "s3"]}, - ) - deprecated_thing_type_name = "my-type-name-deprecated" - client.create_thing_type( - thingTypeName=deprecated_thing_type_name, - thingTypeProperties={"searchableAttributes": ["s1", "s2", "s3"]}, - ) - client.deprecate_thing_type( - thingTypeName=deprecated_thing_type_name, undoDeprecate=False - ) - client.create_thing(thingName=thing_name, thingTypeName=thing_type_name) - with pytest.raises(client.exceptions.InvalidRequestException): - client.update_thing( - thingName=thing_name, thingTypeName=deprecated_thing_type_name - ) - - -class TestDomainConfigurations: - @mock_iot - def test_create_domain_configuration_only_name(self): - client = boto3.client("iot", region_name="us-east-1") - domain_config = client.create_domain_configuration( - domainConfigurationName="testConfig" - ) - domain_config.should.have.key("domainConfigurationName").which.should.equal( - "testConfig" - ) - domain_config.should.have.key("domainConfigurationArn").which.should_not.be.none - - @mock_iot - def test_create_duplicate_domain_configuration_fails(self): - client = boto3.client("iot", region_name="us-east-1") - domain_config = client.create_domain_configuration( - domainConfigurationName="testConfig" - ) - domain_config.should.have.key("domainConfigurationName").which.should.equal( - "testConfig" - ) - domain_config.should.have.key("domainConfigurationArn").which.should_not.be.none - with pytest.raises(client.exceptions.ResourceAlreadyExistsException) as exc: - client.create_domain_configuration(domainConfigurationName="testConfig") - err = exc.value.response["Error"] - err["Code"].should.equal("ResourceAlreadyExistsException") - err["Message"].should.equal( - "Domain configuration with given name already exists." - ) - - @mock_iot - def test_create_domain_configuration_full_params(self): - client = boto3.client("iot", region_name="us-east-1") - domain_config = client.create_domain_configuration( - domainConfigurationName="testConfig", - domainName="example.com", - serverCertificateArns=["ARN1", "ARN2"], - validationCertificateArn="VARN", - authorizerConfig={ - "defaultAuthorizerName": "name", - "allowAuthorizerOverride": True, - }, - serviceType="DATA", - ) - domain_config.should.have.key("domainConfigurationName").which.should.equal( - "testConfig" - ) - domain_config.should.have.key("domainConfigurationArn").which.should_not.be.none - - @mock_iot - def test_create_domain_configuration_invalid_service_type(self): - client = boto3.client("iot", region_name="us-east-1") - with pytest.raises(client.exceptions.InvalidRequestException) as exc: - client.create_domain_configuration( - domainConfigurationName="testConfig", serviceType="INVALIDTYPE" - ) - err = exc.value.response["Error"] - err["Code"].should.equal("InvalidRequestException") - err["Message"].should.equal( - "An error occurred (InvalidRequestException) when calling the DescribeDomainConfiguration operation: Service type INVALIDTYPE not recognized." - ) - - @mock_iot - def test_describe_nonexistent_domain_configuration(self): - client = boto3.client("iot", region_name="us-east-1") - with pytest.raises(client.exceptions.ResourceNotFoundException) as exc: - client.describe_domain_configuration(domainConfigurationName="doesntExist") - err = exc.value.response["Error"] - err["Code"].should.equal("ResourceNotFoundException") - err["Message"].should.equal("The specified resource does not exist.") - - @mock_iot - def test_describe_domain_configuration(self): - client = boto3.client("iot", region_name="us-east-1") - - client.create_domain_configuration( - domainConfigurationName="testConfig", - domainName="example.com", - serverCertificateArns=["ARN1", "ARN2"], - validationCertificateArn="VARN", - authorizerConfig={ - "defaultAuthorizerName": "name", - "allowAuthorizerOverride": True, - }, - serviceType="DATA", - ) - described_config = client.describe_domain_configuration( - domainConfigurationName="testConfig" - ) - described_config.should.have.key("domainConfigurationName").which.should.equal( - "testConfig" - ) - described_config.should.have.key("domainConfigurationArn") - described_config.should.have.key("serverCertificates") - described_config.should.have.key("authorizerConfig") - described_config.should.have.key( - "domainConfigurationStatus" - ).which.should.equal("ENABLED") - described_config.should.have.key("serviceType").which.should.equal("DATA") - described_config.should.have.key("domainType") - described_config.should.have.key("lastStatusChangeDate") - - @mock_iot - def test_update_domain_configuration(self): - client = boto3.client("iot", region_name="us-east-1") - client.create_domain_configuration( - domainConfigurationName="testConfig", - domainName="example.com", - serverCertificateArns=["ARN1", "ARN2"], - validationCertificateArn="VARN", - authorizerConfig={ - "defaultAuthorizerName": "name", - "allowAuthorizerOverride": True, - }, - serviceType="DATA", - ) - client.update_domain_configuration( - domainConfigurationName="testConfig", - authorizerConfig={ - "defaultAuthorizerName": "updatedName", - "allowAuthorizerOverride": False, - }, - domainConfigurationStatus="DISABLED", - ) - described_updated_config = client.describe_domain_configuration( - domainConfigurationName="testConfig" - ) - described_updated_config.should.have.key( - "authorizerConfig" - ).which.should.have.key("defaultAuthorizerName").which.should.equal( - "updatedName" - ) - described_updated_config.should.have.key( - "authorizerConfig" - ).which.should.have.key("allowAuthorizerOverride").which.should.equal(False) - described_updated_config.should.have.key( - "domainConfigurationStatus" - ).which.should.equal("DISABLED") - - @mock_iot - def test_update_domain_configuration_remove_authorizer_type(self): - client = boto3.client("iot", region_name="us-east-1") - client.create_domain_configuration( - domainConfigurationName="testConfig", - domainName="example.com", - serverCertificateArns=["ARN1", "ARN2"], - validationCertificateArn="VARN", - authorizerConfig={ - "defaultAuthorizerName": "name", - "allowAuthorizerOverride": True, - }, - serviceType="DATA", - ) - client.update_domain_configuration( - domainConfigurationName="testConfig", removeAuthorizerConfig=True - ) - described_updated_config = client.describe_domain_configuration( - domainConfigurationName="testConfig" - ) - described_updated_config.should_not.have.key("authorizerConfig") - - @mock_iot - def test_update_nonexistent_domain_configuration(self): - client = boto3.client("iot", region_name="us-east-1") - with pytest.raises(client.exceptions.ResourceNotFoundException) as exc: - client.update_domain_configuration(domainConfigurationName="doesntExist") - err = exc.value.response["Error"] - err["Code"].should.equal("ResourceNotFoundException") - err["Message"].should.equal("The specified resource does not exist.") - - @mock_iot - def test_list_domain_configuration(self): - client = boto3.client("iot", region_name="us-east-1") - client.create_domain_configuration(domainConfigurationName="testConfig1") - client.create_domain_configuration(domainConfigurationName="testConfig2") - domain_configs = client.list_domain_configurations() - domain_configs.should.have.key( - "domainConfigurations" - ).which.should.have.length_of(2) - domain_configs["domainConfigurations"][0].should.have.key( - "domainConfigurationName" - ).which.should.equal("testConfig1") - domain_configs["domainConfigurations"][1].should.have.key( - "domainConfigurationName" - ).which.should.equal("testConfig2") - - @mock_iot - def test_delete_domain_configuration(self): - client = boto3.client("iot", region_name="us-east-1") - client.create_domain_configuration(domainConfigurationName="testConfig") - domain_configs = client.list_domain_configurations() - domain_configs.should.have.key( - "domainConfigurations" - ).which.should.have.length_of(1) - client.delete_domain_configuration(domainConfigurationName="testConfig") - domain_configs = client.list_domain_configurations() - domain_configs.should.have.key( - "domainConfigurations" - ).which.should.have.length_of(0) - - @mock_iot - def test_delete_nonexistent_domain_configuration(self): - client = boto3.client("iot", region_name="us-east-1") - with pytest.raises(client.exceptions.ResourceNotFoundException) as exc: - client.delete_domain_configuration(domainConfigurationName="doesntExist") - err = exc.value.response["Error"] - err["Code"].should.equal("ResourceNotFoundException") - err["Message"].should.equal("The specified resource does not exist.") diff --git a/tests/test_iot/test_iot_certificates.py b/tests/test_iot/test_iot_certificates.py new file mode 100644 index 000000000..b057aa227 --- /dev/null +++ b/tests/test_iot/test_iot_certificates.py @@ -0,0 +1,257 @@ +import boto3 +import pytest + +from botocore.exceptions import ClientError +from moto import mock_iot + + +@mock_iot +def test_certificate_id_generation_deterministic(): + # Creating the same certificate twice should result in the same certificate ID + client = boto3.client("iot", region_name="us-east-1") + cert1 = client.create_keys_and_certificate(setAsActive=False) + client.delete_certificate(certificateId=cert1["certificateId"]) + + cert2 = client.register_certificate( + certificatePem=cert1["certificatePem"], setAsActive=False + ) + cert2.should.have.key("certificateId").which.should.equal(cert1["certificateId"]) + client.delete_certificate(certificateId=cert2["certificateId"]) + + +@mock_iot +def test_create_key_and_certificate(): + client = boto3.client("iot", region_name="us-east-1") + cert = client.create_keys_and_certificate(setAsActive=True) + cert.should.have.key("certificateArn").which.should_not.be.none + cert.should.have.key("certificateId").which.should_not.be.none + cert.should.have.key("certificatePem").which.should_not.be.none + cert.should.have.key("keyPair") + cert["keyPair"].should.have.key("PublicKey").which.should_not.be.none + cert["keyPair"].should.have.key("PrivateKey").which.should_not.be.none + + +@mock_iot +def test_describe_certificate_by_id(): + client = boto3.client("iot", region_name="us-east-1") + cert = client.create_keys_and_certificate(setAsActive=True) + cert_id = cert["certificateId"] + + cert = client.describe_certificate(certificateId=cert_id) + cert.should.have.key("certificateDescription") + cert_desc = cert["certificateDescription"] + cert_desc.should.have.key("certificateArn").which.should_not.be.none + cert_desc.should.have.key("certificateId").which.should_not.be.none + cert_desc.should.have.key("certificatePem").which.should_not.be.none + cert_desc.should.have.key("validity").which.should_not.be.none + validity = cert_desc["validity"] + validity.should.have.key("notBefore").which.should_not.be.none + validity.should.have.key("notAfter").which.should_not.be.none + cert_desc.should.have.key("status").which.should.equal("ACTIVE") + + +@mock_iot +def test_list_certificates(): + client = boto3.client("iot", region_name="us-east-1") + cert = client.create_keys_and_certificate(setAsActive=True) + cert_id = cert["certificateId"] + + res = client.list_certificates() + for cert in res["certificates"]: + cert.should.have.key("certificateArn").which.should_not.be.none + cert.should.have.key("certificateId").which.should_not.be.none + cert.should.have.key("status").which.should_not.be.none + cert.should.have.key("creationDate").which.should_not.be.none + + client.update_certificate(certificateId=cert_id, newStatus="REVOKED") + cert = client.describe_certificate(certificateId=cert_id) + cert_desc = cert["certificateDescription"] + cert_desc.should.have.key("status").which.should.equal("REVOKED") + + +@mock_iot +def test_update_certificate(): + client = boto3.client("iot", region_name="us-east-1") + cert = client.create_keys_and_certificate(setAsActive=True) + cert_id = cert["certificateId"] + + client.update_certificate(certificateId=cert_id, newStatus="REVOKED") + cert = client.describe_certificate(certificateId=cert_id) + cert_desc = cert["certificateDescription"] + cert_desc.should.have.key("status").which.should.equal("REVOKED") + + +@pytest.mark.parametrize("status", ["REVOKED", "INACTIVE"]) +@mock_iot +def test_delete_certificate_with_status(status): + client = boto3.client("iot", region_name="us-east-1") + cert = client.create_keys_and_certificate(setAsActive=True) + cert_id = cert["certificateId"] + + # Ensure certificate has the right status before we can delete + client.update_certificate(certificateId=cert_id, newStatus=status) + + client.delete_certificate(certificateId=cert_id) + res = client.list_certificates() + res.should.have.key("certificates").equals([]) + + +@mock_iot +def test_register_certificate_without_ca(): + client = boto3.client("iot", region_name="us-east-1") + cert = client.create_keys_and_certificate(setAsActive=True) + cert_id = cert["certificateId"] + cert_pem = cert["certificatePem"] + + client.update_certificate(certificateId=cert_id, newStatus="REVOKED") + client.delete_certificate(certificateId=cert_id) + + # Test register_certificate without CA flow + cert = client.register_certificate_without_ca( + certificatePem=cert_pem, status="INACTIVE" + ) + cert.should.have.key("certificateId").which.should_not.be.none + cert.should.have.key("certificateArn").which.should_not.be.none + cert_id = cert["certificateId"] + + res = client.list_certificates() + res.should.have.key("certificates").which.should.have.length_of(1) + for cert in res["certificates"]: + cert.should.have.key("certificateArn").which.should_not.be.none + cert.should.have.key("certificateId").which.should_not.be.none + cert.should.have.key("status").which.should_not.be.none + cert.should.have.key("creationDate").which.should_not.be.none + + client.delete_certificate(certificateId=cert_id) + res = client.list_certificates() + res.should.have.key("certificates") + + +@mock_iot +def test_create_certificate_validation(): + # Test we can't create a cert that already exists + client = boto3.client("iot", region_name="us-east-1") + cert = client.create_keys_and_certificate(setAsActive=False) + + with pytest.raises(ClientError) as e: + client.register_certificate( + certificatePem=cert["certificatePem"], setAsActive=False + ) + e.value.response["Error"]["Message"].should.contain( + "The certificate is already provisioned or registered" + ) + + with pytest.raises(ClientError) as e: + client.register_certificate_without_ca( + certificatePem=cert["certificatePem"], status="ACTIVE" + ) + e.value.response["Error"]["Message"].should.contain( + "The certificate is already provisioned or registered" + ) + + +@mock_iot +def test_delete_certificate_validation(): + doc = """{ + "Version": "2012-10-17", + "Statement":[ + { + "Effect":"Allow", + "Action":[ + "iot: *" + ], + "Resource":"*" + } + ] + } + """ + client = boto3.client("iot", region_name="ap-northeast-1") + cert = client.create_keys_and_certificate(setAsActive=True) + cert_id = cert["certificateId"] + cert_arn = cert["certificateArn"] + policy_name = "my-policy" + thing_name = "thing-1" + client.create_policy(policyName=policy_name, policyDocument=doc) + client.attach_principal_policy(policyName=policy_name, principal=cert_arn) + client.create_thing(thingName=thing_name) + client.attach_thing_principal(thingName=thing_name, principal=cert_arn) + + with pytest.raises(ClientError) as e: + client.delete_certificate(certificateId=cert_id) + e.value.response["Error"]["Message"].should.contain( + "Certificate must be deactivated (not ACTIVE) before deletion." + ) + res = client.list_certificates() + res.should.have.key("certificates").which.should.have.length_of(1) + + client.update_certificate(certificateId=cert_id, newStatus="REVOKED") + with pytest.raises(ClientError) as e: + client.delete_certificate(certificateId=cert_id) + e.value.response["Error"]["Message"].should.contain( + "Things must be detached before deletion (arn: %s)" % cert_arn + ) + res = client.list_certificates() + res.should.have.key("certificates").which.should.have.length_of(1) + + client.detach_thing_principal(thingName=thing_name, principal=cert_arn) + with pytest.raises(ClientError) as e: + client.delete_certificate(certificateId=cert_id) + e.value.response["Error"]["Message"].should.contain( + "Certificate policies must be detached before deletion (arn: %s)" % cert_arn + ) + res = client.list_certificates() + res.should.have.key("certificates").which.should.have.length_of(1) + + client.detach_principal_policy(policyName=policy_name, principal=cert_arn) + client.delete_certificate(certificateId=cert_id) + res = client.list_certificates() + res.should.have.key("certificates").which.should.have.length_of(0) + + +@mock_iot +def test_delete_thing_with_certificate_validation(): + client = boto3.client("iot", region_name="ap-northeast-1") + cert = client.create_keys_and_certificate(setAsActive=True) + cert_id = cert["certificateId"] + cert_arn = cert["certificateArn"] + thing_name = "thing-1" + client.create_thing(thingName=thing_name) + client.attach_thing_principal(thingName=thing_name, principal=cert_arn) + + client.update_certificate(certificateId=cert_id, newStatus="REVOKED") + + with pytest.raises(ClientError) as e: + client.delete_thing(thingName=thing_name) + err = e.value.response["Error"] + err["Code"].should.equal("InvalidRequestException") + err["Message"].should.equals( + f"Cannot delete. Thing {thing_name} is still attached to one or more principals" + ) + + client.detach_thing_principal(thingName=thing_name, principal=cert_arn) + + client.delete_thing(thingName=thing_name) + + # Certificate still exists + res = client.list_certificates() + res.should.have.key("certificates").which.should.have.length_of(1) + res["certificates"][0]["certificateArn"].should.equal(cert_arn) + res["certificates"][0]["status"].should.equal("REVOKED") + + +@mock_iot +def test_certs_create_inactive(): + client = boto3.client("iot", region_name="ap-northeast-1") + cert = client.create_keys_and_certificate(setAsActive=False) + cert_id = cert["certificateId"] + + cert = client.describe_certificate(certificateId=cert_id) + cert.should.have.key("certificateDescription") + cert_desc = cert["certificateDescription"] + cert_desc.should.have.key("status").which.should.equal("INACTIVE") + + client.update_certificate(certificateId=cert_id, newStatus="ACTIVE") + cert = client.describe_certificate(certificateId=cert_id) + cert.should.have.key("certificateDescription") + cert_desc = cert["certificateDescription"] + cert_desc.should.have.key("status").which.should.equal("ACTIVE") diff --git a/tests/test_iot/test_iot_deprecate_thing_type.py b/tests/test_iot/test_iot_deprecate_thing_type.py new file mode 100644 index 000000000..6bee18d71 --- /dev/null +++ b/tests/test_iot/test_iot_deprecate_thing_type.py @@ -0,0 +1,72 @@ +import boto3 +import pytest + +from moto import mock_iot + + +@mock_iot +def test_deprecate_undeprecate_thing_type(): + client = boto3.client("iot", region_name="ap-northeast-1") + thing_type_name = "my-type-name" + client.create_thing_type( + thingTypeName=thing_type_name, + thingTypeProperties={"searchableAttributes": ["s1", "s2", "s3"]}, + ) + + res = client.describe_thing_type(thingTypeName=thing_type_name) + res["thingTypeMetadata"]["deprecated"].should.equal(False) + client.deprecate_thing_type(thingTypeName=thing_type_name, undoDeprecate=False) + + res = client.describe_thing_type(thingTypeName=thing_type_name) + res["thingTypeMetadata"]["deprecated"].should.equal(True) + + client.deprecate_thing_type(thingTypeName=thing_type_name, undoDeprecate=True) + + res = client.describe_thing_type(thingTypeName=thing_type_name) + res["thingTypeMetadata"]["deprecated"].should.equal(False) + + +@mock_iot +def test_deprecate_thing_type_not_exist(): + client = boto3.client("iot", region_name="ap-northeast-1") + thing_type_name = "my-type-name" + with pytest.raises(client.exceptions.ResourceNotFoundException): + client.deprecate_thing_type(thingTypeName=thing_type_name, undoDeprecate=False) + + +@mock_iot +def test_create_thing_with_deprecated_type(): + client = boto3.client("iot", region_name="ap-northeast-1") + thing_type_name = "my-type-name" + client.create_thing_type( + thingTypeName=thing_type_name, + thingTypeProperties={"searchableAttributes": ["s1", "s2", "s3"]}, + ) + client.deprecate_thing_type(thingTypeName=thing_type_name, undoDeprecate=False) + with pytest.raises(client.exceptions.InvalidRequestException): + client.create_thing(thingName="thing-name", thingTypeName=thing_type_name) + + +@mock_iot +def test_update_thing_with_deprecated_type(): + client = boto3.client("iot", region_name="ap-northeast-1") + thing_type_name = "my-type-name" + thing_name = "thing-name" + + client.create_thing_type( + thingTypeName=thing_type_name, + thingTypeProperties={"searchableAttributes": ["s1", "s2", "s3"]}, + ) + deprecated_thing_type_name = "my-type-name-deprecated" + client.create_thing_type( + thingTypeName=deprecated_thing_type_name, + thingTypeProperties={"searchableAttributes": ["s1", "s2", "s3"]}, + ) + client.deprecate_thing_type( + thingTypeName=deprecated_thing_type_name, undoDeprecate=False + ) + client.create_thing(thingName=thing_name, thingTypeName=thing_type_name) + with pytest.raises(client.exceptions.InvalidRequestException): + client.update_thing( + thingName=thing_name, thingTypeName=deprecated_thing_type_name + ) diff --git a/tests/test_iot/test_iot_domain_configuration.py b/tests/test_iot/test_iot_domain_configuration.py new file mode 100644 index 000000000..351f5a4d4 --- /dev/null +++ b/tests/test_iot/test_iot_domain_configuration.py @@ -0,0 +1,220 @@ +import boto3 +import pytest + +from moto import mock_iot + + +@mock_iot +def test_create_domain_configuration_only_name(): + client = boto3.client("iot", region_name="us-east-1") + domain_config = client.create_domain_configuration( + domainConfigurationName="testConfig" + ) + domain_config.should.have.key("domainConfigurationName").which.should.equal( + "testConfig" + ) + domain_config.should.have.key("domainConfigurationArn").which.should_not.be.none + + +@mock_iot +def test_create_duplicate_domain_configuration_fails(): + client = boto3.client("iot", region_name="us-east-1") + domain_config = client.create_domain_configuration( + domainConfigurationName="testConfig" + ) + domain_config.should.have.key("domainConfigurationName").which.should.equal( + "testConfig" + ) + domain_config.should.have.key("domainConfigurationArn").which.should_not.be.none + with pytest.raises(client.exceptions.ResourceAlreadyExistsException) as exc: + client.create_domain_configuration(domainConfigurationName="testConfig") + err = exc.value.response["Error"] + err["Code"].should.equal("ResourceAlreadyExistsException") + err["Message"].should.equal("Domain configuration with given name already exists.") + + +@mock_iot +def test_create_domain_configuration_full_params(): + client = boto3.client("iot", region_name="us-east-1") + domain_config = client.create_domain_configuration( + domainConfigurationName="testConfig", + domainName="example.com", + serverCertificateArns=["ARN1", "ARN2"], + validationCertificateArn="VARN", + authorizerConfig={ + "defaultAuthorizerName": "name", + "allowAuthorizerOverride": True, + }, + serviceType="DATA", + ) + domain_config.should.have.key("domainConfigurationName").which.should.equal( + "testConfig" + ) + domain_config.should.have.key("domainConfigurationArn").which.should_not.be.none + + +@mock_iot +def test_create_domain_configuration_invalid_service_type(): + client = boto3.client("iot", region_name="us-east-1") + with pytest.raises(client.exceptions.InvalidRequestException) as exc: + client.create_domain_configuration( + domainConfigurationName="testConfig", serviceType="INVALIDTYPE" + ) + err = exc.value.response["Error"] + err["Code"].should.equal("InvalidRequestException") + err["Message"].should.equal( + "An error occurred (InvalidRequestException) when calling the DescribeDomainConfiguration operation: Service type INVALIDTYPE not recognized." + ) + + +@mock_iot +def test_describe_nonexistent_domain_configuration(): + client = boto3.client("iot", region_name="us-east-1") + with pytest.raises(client.exceptions.ResourceNotFoundException) as exc: + client.describe_domain_configuration(domainConfigurationName="doesntExist") + err = exc.value.response["Error"] + err["Code"].should.equal("ResourceNotFoundException") + err["Message"].should.equal("The specified resource does not exist.") + + +@mock_iot +def test_describe_domain_configuration(): + client = boto3.client("iot", region_name="us-east-1") + + client.create_domain_configuration( + domainConfigurationName="testConfig", + domainName="example.com", + serverCertificateArns=["ARN1", "ARN2"], + validationCertificateArn="VARN", + authorizerConfig={ + "defaultAuthorizerName": "name", + "allowAuthorizerOverride": True, + }, + serviceType="DATA", + ) + described_config = client.describe_domain_configuration( + domainConfigurationName="testConfig" + ) + described_config.should.have.key("domainConfigurationName").which.should.equal( + "testConfig" + ) + described_config.should.have.key("domainConfigurationArn") + described_config.should.have.key("serverCertificates") + described_config.should.have.key("authorizerConfig") + described_config.should.have.key("domainConfigurationStatus").which.should.equal( + "ENABLED" + ) + described_config.should.have.key("serviceType").which.should.equal("DATA") + described_config.should.have.key("domainType") + described_config.should.have.key("lastStatusChangeDate") + + +@mock_iot +def test_update_domain_configuration(): + client = boto3.client("iot", region_name="us-east-1") + client.create_domain_configuration( + domainConfigurationName="testConfig", + domainName="example.com", + serverCertificateArns=["ARN1", "ARN2"], + validationCertificateArn="VARN", + authorizerConfig={ + "defaultAuthorizerName": "name", + "allowAuthorizerOverride": True, + }, + serviceType="DATA", + ) + client.update_domain_configuration( + domainConfigurationName="testConfig", + authorizerConfig={ + "defaultAuthorizerName": "updatedName", + "allowAuthorizerOverride": False, + }, + domainConfigurationStatus="DISABLED", + ) + described_updated_config = client.describe_domain_configuration( + domainConfigurationName="testConfig" + ) + described_updated_config.should.have.key("authorizerConfig").which.should.have.key( + "defaultAuthorizerName" + ).which.should.equal("updatedName") + described_updated_config.should.have.key("authorizerConfig").which.should.have.key( + "allowAuthorizerOverride" + ).which.should.equal(False) + described_updated_config.should.have.key( + "domainConfigurationStatus" + ).which.should.equal("DISABLED") + + +@mock_iot +def test_update_domain_configuration_remove_authorizer_type(): + client = boto3.client("iot", region_name="us-east-1") + client.create_domain_configuration( + domainConfigurationName="testConfig", + domainName="example.com", + serverCertificateArns=["ARN1", "ARN2"], + validationCertificateArn="VARN", + authorizerConfig={ + "defaultAuthorizerName": "name", + "allowAuthorizerOverride": True, + }, + serviceType="DATA", + ) + client.update_domain_configuration( + domainConfigurationName="testConfig", removeAuthorizerConfig=True + ) + described_updated_config = client.describe_domain_configuration( + domainConfigurationName="testConfig" + ) + described_updated_config.should_not.have.key("authorizerConfig") + + +@mock_iot +def test_update_nonexistent_domain_configuration(): + client = boto3.client("iot", region_name="us-east-1") + with pytest.raises(client.exceptions.ResourceNotFoundException) as exc: + client.update_domain_configuration(domainConfigurationName="doesntExist") + err = exc.value.response["Error"] + err["Code"].should.equal("ResourceNotFoundException") + err["Message"].should.equal("The specified resource does not exist.") + + +@mock_iot +def test_list_domain_configuration(): + client = boto3.client("iot", region_name="us-east-1") + client.create_domain_configuration(domainConfigurationName="testConfig1") + client.create_domain_configuration(domainConfigurationName="testConfig2") + domain_configs = client.list_domain_configurations() + domain_configs.should.have.key("domainConfigurations").which.should.have.length_of( + 2 + ) + domain_configs["domainConfigurations"][0].should.have.key( + "domainConfigurationName" + ).which.should.equal("testConfig1") + domain_configs["domainConfigurations"][1].should.have.key( + "domainConfigurationName" + ).which.should.equal("testConfig2") + + +@mock_iot +def test_delete_domain_configuration(): + client = boto3.client("iot", region_name="us-east-1") + client.create_domain_configuration(domainConfigurationName="testConfig") + domain_configs = client.list_domain_configurations() + domain_configs.should.have.key("domainConfigurations").which.should.have.length_of( + 1 + ) + client.delete_domain_configuration(domainConfigurationName="testConfig") + domain_configs = client.list_domain_configurations() + domain_configs.should.have.key("domainConfigurations").which.should.have.length_of( + 0 + ) + + +@mock_iot +def test_delete_nonexistent_domain_configuration(): + client = boto3.client("iot", region_name="us-east-1") + with pytest.raises(client.exceptions.ResourceNotFoundException) as exc: + client.delete_domain_configuration(domainConfigurationName="doesntExist") + err = exc.value.response["Error"] + err["Code"].should.equal("ResourceNotFoundException") + err["Message"].should.equal("The specified resource does not exist.") diff --git a/tests/test_iot/test_iot_job_executions.py b/tests/test_iot/test_iot_job_executions.py new file mode 100644 index 000000000..980f8021c --- /dev/null +++ b/tests/test_iot/test_iot_job_executions.py @@ -0,0 +1,287 @@ +import boto3 +import json +import pytest + +from botocore.exceptions import ClientError +from moto import mock_iot + + +@mock_iot +def test_describe_job_execution(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # job document + job_document = {"field": "value"} + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + description="Description", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + job.should.have.key("description") + + job_execution = client.describe_job_execution(jobId=job_id, thingName=name) + job_execution.should.have.key("execution") + job_execution["execution"].should.have.key("jobId").which.should.equal(job_id) + job_execution["execution"].should.have.key("status").which.should.equal("QUEUED") + job_execution["execution"].should.have.key("forceCanceled").which.should.equal( + False + ) + job_execution["execution"].should.have.key("statusDetails").which.should.equal( + {"detailsMap": {}} + ) + job_execution["execution"].should.have.key("thingArn").which.should.equal( + thing["thingArn"] + ) + job_execution["execution"].should.have.key("queuedAt") + job_execution["execution"].should.have.key("startedAt") + job_execution["execution"].should.have.key("lastUpdatedAt") + job_execution["execution"].should.have.key("executionNumber").which.should.equal( + 123 + ) + job_execution["execution"].should.have.key("versionNumber").which.should.equal(123) + job_execution["execution"].should.have.key( + "approximateSecondsBeforeTimedOut" + ).which.should.equal(123) + + job_execution = client.describe_job_execution( + jobId=job_id, thingName=name, executionNumber=123 + ) + job_execution.should.have.key("execution") + job_execution["execution"].should.have.key("jobId").which.should.equal(job_id) + job_execution["execution"].should.have.key("status").which.should.equal("QUEUED") + job_execution["execution"].should.have.key("forceCanceled").which.should.equal( + False + ) + job_execution["execution"].should.have.key("statusDetails").which.should.equal( + {"detailsMap": {}} + ) + job_execution["execution"].should.have.key("thingArn").which.should.equal( + thing["thingArn"] + ) + job_execution["execution"].should.have.key("queuedAt") + job_execution["execution"].should.have.key("startedAt") + job_execution["execution"].should.have.key("lastUpdatedAt") + job_execution["execution"].should.have.key("executionNumber").which.should.equal( + 123 + ) + job_execution["execution"].should.have.key("versionNumber").which.should.equal(123) + job_execution["execution"].should.have.key( + "approximateSecondsBeforeTimedOut" + ).which.should.equal(123) + + with pytest.raises(ClientError) as exc: + client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=456) + error_code = exc.value.response["Error"]["Code"] + error_code.should.equal("ResourceNotFoundException") + + +@mock_iot +def test_cancel_job_execution(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # job document + job_document = {"field": "value"} + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + description="Description", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + job.should.have.key("description") + + client.cancel_job_execution(jobId=job_id, thingName=name) + job_execution = client.describe_job_execution(jobId=job_id, thingName=name) + job_execution.should.have.key("execution") + job_execution["execution"].should.have.key("status").which.should.equal("CANCELED") + + +@mock_iot +def test_delete_job_execution(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # job document + job_document = {"field": "value"} + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + description="Description", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + job.should.have.key("description") + + client.delete_job_execution(jobId=job_id, thingName=name, executionNumber=123) + + with pytest.raises(ClientError) as exc: + client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=123) + error_code = exc.value.response["Error"]["Code"] + error_code.should.equal("ResourceNotFoundException") + + +@mock_iot +def test_list_job_executions_for_job(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # job document + job_document = {"field": "value"} + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + description="Description", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + job.should.have.key("description") + + job_execution = client.list_job_executions_for_job(jobId=job_id) + job_execution.should.have.key("executionSummaries") + job_execution["executionSummaries"][0].should.have.key( + "thingArn" + ).which.should.equal(thing["thingArn"]) + + job_execution = client.list_job_executions_for_job(jobId=job_id, status="QUEUED") + job_execution.should.have.key("executionSummaries") + job_execution["executionSummaries"][0].should.have.key( + "thingArn" + ).which.should.equal(thing["thingArn"]) + + +@mock_iot +def test_list_job_executions_for_thing(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # job document + job_document = {"field": "value"} + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + description="Description", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + job.should.have.key("description") + + job_execution = client.list_job_executions_for_thing(thingName=name) + job_execution.should.have.key("executionSummaries") + job_execution["executionSummaries"][0].should.have.key("jobId").which.should.equal( + job_id + ) + + job_execution = client.list_job_executions_for_thing( + thingName=name, status="QUEUED" + ) + job_execution.should.have.key("executionSummaries") + job_execution["executionSummaries"][0].should.have.key("jobId").which.should.equal( + job_id + ) + + +@mock_iot +def test_list_job_executions_for_thing_paginated(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + thing = client.create_thing(thingName=name) + + for idx in range(0, 10): + client.create_job( + jobId=f"TestJob_{idx}", + targets=[thing["thingArn"]], + document=json.dumps({"field": "value"}), + ) + + res = client.list_job_executions_for_thing(thingName=name, maxResults=2) + executions = res["executionSummaries"] + executions.should.have.length_of(2) + res.should.have.key("nextToken") + + res = client.list_job_executions_for_thing( + thingName=name, maxResults=1, nextToken=res["nextToken"] + ) + executions = res["executionSummaries"] + executions.should.have.length_of(1) + res.should.have.key("nextToken") + + res = client.list_job_executions_for_thing( + thingName=name, nextToken=res["nextToken"] + ) + executions = res["executionSummaries"] + executions.should.have.length_of(7) + res.shouldnt.have.key("nextToken") diff --git a/tests/test_iot/test_iot_jobs.py b/tests/test_iot/test_iot_jobs.py new file mode 100644 index 000000000..48c43989c --- /dev/null +++ b/tests/test_iot/test_iot_jobs.py @@ -0,0 +1,346 @@ +import boto3 +import json + +from moto import mock_iot + + +@mock_iot +def test_create_job(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing# job document + # job_document = { + # "field": "value" + # } + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # job document + job_document = {"field": "value"} + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + description="Description", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + job.should.have.key("description") + + +@mock_iot +def test_list_jobs(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing# job document + # job_document = { + # "field": "value" + # } + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # job document + job_document = {"field": "value"} + + job1 = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + description="Description", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job1.should.have.key("jobId").which.should.equal(job_id) + job1.should.have.key("jobArn") + job1.should.have.key("description") + + job2 = client.create_job( + jobId=job_id + "1", + targets=[thing["thingArn"]], + document=json.dumps(job_document), + description="Description", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job2.should.have.key("jobId").which.should.equal(job_id + "1") + job2.should.have.key("jobArn") + job2.should.have.key("description") + + jobs = client.list_jobs() + jobs.should.have.key("jobs") + jobs.should_not.have.key("nextToken") + jobs["jobs"][0].should.have.key("jobId").which.should.equal(job_id) + jobs["jobs"][1].should.have.key("jobId").which.should.equal(job_id + "1") + + +@mock_iot +def test_describe_job(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + + job = client.describe_job(jobId=job_id) + job.should.have.key("documentSource") + job.should.have.key("job") + job.should.have.key("job").which.should.have.key("jobArn") + job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("job").which.should.have.key("targets") + job.should.have.key("job").which.should.have.key("jobProcessDetails") + job.should.have.key("job").which.should.have.key("lastUpdatedAt") + job.should.have.key("job").which.should.have.key("createdAt") + job.should.have.key("job").which.should.have.key("jobExecutionsRolloutConfig") + job.should.have.key("job").which.should.have.key( + "targetSelection" + ).which.should.equal("CONTINUOUS") + job.should.have.key("job").which.should.have.key("presignedUrlConfig") + job.should.have.key("job").which.should.have.key( + "presignedUrlConfig" + ).which.should.have.key("roleArn").which.should.equal( + "arn:aws:iam::1:role/service-role/iot_job_role" + ) + job.should.have.key("job").which.should.have.key( + "presignedUrlConfig" + ).which.should.have.key("expiresInSec").which.should.equal(123) + job.should.have.key("job").which.should.have.key( + "jobExecutionsRolloutConfig" + ).which.should.have.key("maximumPerMinute").which.should.equal(10) + + +@mock_iot +def test_describe_job_1(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # job document + job_document = {"field": "value"} + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + + job = client.describe_job(jobId=job_id) + job.should.have.key("job") + job.should.have.key("job").which.should.have.key("jobArn") + job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("job").which.should.have.key("targets") + job.should.have.key("job").which.should.have.key("jobProcessDetails") + job.should.have.key("job").which.should.have.key("lastUpdatedAt") + job.should.have.key("job").which.should.have.key("createdAt") + job.should.have.key("job").which.should.have.key("jobExecutionsRolloutConfig") + job.should.have.key("job").which.should.have.key( + "targetSelection" + ).which.should.equal("CONTINUOUS") + job.should.have.key("job").which.should.have.key("presignedUrlConfig") + job.should.have.key("job").which.should.have.key( + "presignedUrlConfig" + ).which.should.have.key("roleArn").which.should.equal( + "arn:aws:iam::1:role/service-role/iot_job_role" + ) + job.should.have.key("job").which.should.have.key( + "presignedUrlConfig" + ).which.should.have.key("expiresInSec").which.should.equal(123) + job.should.have.key("job").which.should.have.key( + "jobExecutionsRolloutConfig" + ).which.should.have.key("maximumPerMinute").which.should.equal(10) + + +@mock_iot +def test_delete_job(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + + job = client.describe_job(jobId=job_id) + job.should.have.key("job") + job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) + + client.delete_job(jobId=job_id) + + client.list_jobs()["jobs"].should.have.length_of(0) + + +@mock_iot +def test_cancel_job(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + + job = client.describe_job(jobId=job_id) + job.should.have.key("job") + job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) + + job = client.cancel_job(jobId=job_id, reasonCode="Because", comment="You are") + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + + job = client.describe_job(jobId=job_id) + job.should.have.key("job") + job.should.have.key("job").which.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("job").which.should.have.key("status").which.should.equal( + "CANCELED" + ) + job.should.have.key("job").which.should.have.key( + "forceCanceled" + ).which.should.equal(False) + job.should.have.key("job").which.should.have.key("reasonCode").which.should.equal( + "Because" + ) + job.should.have.key("job").which.should.have.key("comment").which.should.equal( + "You are" + ) + + +@mock_iot +def test_get_job_document_with_document_source(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + + job_document = client.get_job_document(jobId=job_id) + job_document.should.have.key("document").which.should.equal("") + + +@mock_iot +def test_get_job_document_with_document(): + client = boto3.client("iot", region_name="eu-west-1") + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # job document + job_document = {"field": "value"} + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + presignedUrlConfig={ + "roleArn": "arn:aws:iam::1:role/service-role/iot_job_role", + "expiresInSec": 123, + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={"maximumPerMinute": 10}, + ) + + job.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key("jobArn") + + job_document = client.get_job_document(jobId=job_id) + job_document.should.have.key("document").which.should.equal('{"field": "value"}') diff --git a/tests/test_iot/test_iot_policies.py b/tests/test_iot/test_iot_policies.py new file mode 100644 index 000000000..ba3a0f1ff --- /dev/null +++ b/tests/test_iot/test_iot_policies.py @@ -0,0 +1,306 @@ +import boto3 +import json +import pytest + +from botocore.exceptions import ClientError +from moto import mock_iot, mock_cognitoidentity + + +@mock_iot +def test_attach_policy(): + client = boto3.client("iot", region_name="ap-northeast-1") + policy_name = "my-policy" + doc = "{}" + + cert = client.create_keys_and_certificate(setAsActive=True) + cert_arn = cert["certificateArn"] + client.create_policy(policyName=policy_name, policyDocument=doc) + client.attach_policy(policyName=policy_name, target=cert_arn) + + res = client.list_attached_policies(target=cert_arn) + res.should.have.key("policies").which.should.have.length_of(1) + res["policies"][0]["policyName"].should.equal("my-policy") + + +@mock_iot +@mock_cognitoidentity +def test_attach_policy_to_identity(): + region = "ap-northeast-1" + + cognito_identity_client = boto3.client("cognito-identity", region_name=region) + identity_pool_name = "test_identity_pool" + identity_pool = cognito_identity_client.create_identity_pool( + IdentityPoolName=identity_pool_name, AllowUnauthenticatedIdentities=True + ) + identity = cognito_identity_client.get_id( + AccountId="test", IdentityPoolId=identity_pool["IdentityPoolId"] + ) + + client = boto3.client("iot", region_name=region) + policy_name = "my-policy" + doc = "{}" + client.create_policy(policyName=policy_name, policyDocument=doc) + client.attach_policy(policyName=policy_name, target=identity["IdentityId"]) + + res = client.list_attached_policies(target=identity["IdentityId"]) + res.should.have.key("policies").which.should.have.length_of(1) + res["policies"][0]["policyName"].should.equal(policy_name) + + +@mock_iot +def test_detach_policy(): + client = boto3.client("iot", region_name="ap-northeast-1") + policy_name = "my-policy" + doc = "{}" + + cert = client.create_keys_and_certificate(setAsActive=True) + cert_arn = cert["certificateArn"] + client.create_policy(policyName=policy_name, policyDocument=doc) + client.attach_policy(policyName=policy_name, target=cert_arn) + + res = client.list_attached_policies(target=cert_arn) + res.should.have.key("policies").which.should.have.length_of(1) + res["policies"][0]["policyName"].should.equal("my-policy") + + client.detach_policy(policyName=policy_name, target=cert_arn) + res = client.list_attached_policies(target=cert_arn) + res.should.have.key("policies").which.should.be.empty + + +@mock_iot +def test_list_attached_policies(): + client = boto3.client("iot", region_name="ap-northeast-1") + cert = client.create_keys_and_certificate(setAsActive=True) + policies = client.list_attached_policies(target=cert["certificateArn"]) + policies["policies"].should.be.empty + + +@mock_iot +def test_policy_versions(): + client = boto3.client("iot", region_name="ap-northeast-1") + policy_name = "my-policy" + doc = "{}" + + policy = client.create_policy(policyName=policy_name, policyDocument=doc) + policy.should.have.key("policyName").which.should.equal(policy_name) + policy.should.have.key("policyArn").which.should_not.be.none + policy.should.have.key("policyDocument").which.should.equal(json.dumps({})) + policy.should.have.key("policyVersionId").which.should.equal("1") + + policy = client.get_policy(policyName=policy_name) + policy.should.have.key("policyName").which.should.equal(policy_name) + policy.should.have.key("policyArn").which.should_not.be.none + policy.should.have.key("policyDocument").which.should.equal(json.dumps({})) + policy.should.have.key("defaultVersionId").which.should.equal( + policy["defaultVersionId"] + ) + + policy1 = client.create_policy_version( + policyName=policy_name, + policyDocument=json.dumps({"version": "version_1"}), + setAsDefault=True, + ) + policy1.should.have.key("policyArn").which.should_not.be.none + policy1.should.have.key("policyDocument").which.should.equal( + json.dumps({"version": "version_1"}) + ) + policy1.should.have.key("policyVersionId").which.should.equal("2") + policy1.should.have.key("isDefaultVersion").which.should.equal(True) + + policy2 = client.create_policy_version( + policyName=policy_name, + policyDocument=json.dumps({"version": "version_2"}), + setAsDefault=False, + ) + policy2.should.have.key("policyArn").which.should_not.be.none + policy2.should.have.key("policyDocument").which.should.equal( + json.dumps({"version": "version_2"}) + ) + policy2.should.have.key("policyVersionId").which.should.equal("3") + policy2.should.have.key("isDefaultVersion").which.should.equal(False) + + policy = client.get_policy(policyName=policy_name) + policy.should.have.key("policyName").which.should.equal(policy_name) + policy.should.have.key("policyArn").which.should_not.be.none + policy.should.have.key("policyDocument").which.should.equal( + json.dumps({"version": "version_1"}) + ) + policy.should.have.key("defaultVersionId").which.should.equal( + policy1["policyVersionId"] + ) + + policy3 = client.create_policy_version( + policyName=policy_name, + policyDocument=json.dumps({"version": "version_3"}), + setAsDefault=False, + ) + policy3.should.have.key("policyArn").which.should_not.be.none + policy3.should.have.key("policyDocument").which.should.equal( + json.dumps({"version": "version_3"}) + ) + policy3.should.have.key("policyVersionId").which.should.equal("4") + policy3.should.have.key("isDefaultVersion").which.should.equal(False) + + policy4 = client.create_policy_version( + policyName=policy_name, + policyDocument=json.dumps({"version": "version_4"}), + setAsDefault=False, + ) + policy4.should.have.key("policyArn").which.should_not.be.none + policy4.should.have.key("policyDocument").which.should.equal( + json.dumps({"version": "version_4"}) + ) + policy4.should.have.key("policyVersionId").which.should.equal("5") + policy4.should.have.key("isDefaultVersion").which.should.equal(False) + + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key("policyVersions").which.should.have.length_of(5) + list( + map(lambda item: item["isDefaultVersion"], policy_versions["policyVersions"]) + ).count(True).should.equal(1) + default_policy = list( + filter(lambda item: item["isDefaultVersion"], policy_versions["policyVersions"]) + ) + default_policy[0].should.have.key("versionId").should.equal( + policy1["policyVersionId"] + ) + + policy = client.get_policy(policyName=policy_name) + policy.should.have.key("policyName").which.should.equal(policy_name) + policy.should.have.key("policyArn").which.should_not.be.none + policy.should.have.key("policyDocument").which.should.equal( + json.dumps({"version": "version_1"}) + ) + policy.should.have.key("defaultVersionId").which.should.equal( + policy1["policyVersionId"] + ) + + client.set_default_policy_version( + policyName=policy_name, policyVersionId=policy4["policyVersionId"] + ) + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key("policyVersions").which.should.have.length_of(5) + list( + map(lambda item: item["isDefaultVersion"], policy_versions["policyVersions"]) + ).count(True).should.equal(1) + default_policy = list( + filter(lambda item: item["isDefaultVersion"], policy_versions["policyVersions"]) + ) + default_policy[0].should.have.key("versionId").should.equal( + policy4["policyVersionId"] + ) + + policy = client.get_policy(policyName=policy_name) + policy.should.have.key("policyName").which.should.equal(policy_name) + policy.should.have.key("policyArn").which.should_not.be.none + policy.should.have.key("policyDocument").which.should.equal( + json.dumps({"version": "version_4"}) + ) + policy.should.have.key("defaultVersionId").which.should.equal( + policy4["policyVersionId"] + ) + + with pytest.raises(ClientError) as exc: + client.create_policy_version( + policyName=policy_name, + policyDocument=json.dumps({"version": "version_5"}), + setAsDefault=False, + ) + err = exc.value.response["Error"] + err["Message"].should.equal( + "The policy %s already has the maximum number of versions (5)" % policy_name + ) + + client.delete_policy_version(policyName=policy_name, policyVersionId="1") + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key("policyVersions").which.should.have.length_of(4) + + client.delete_policy_version( + policyName=policy_name, policyVersionId=policy1["policyVersionId"] + ) + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key("policyVersions").which.should.have.length_of(3) + client.delete_policy_version( + policyName=policy_name, policyVersionId=policy2["policyVersionId"] + ) + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key("policyVersions").which.should.have.length_of(2) + + client.delete_policy_version( + policyName=policy_name, policyVersionId=policy3["policyVersionId"] + ) + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key("policyVersions").which.should.have.length_of(1) + + # should fail as it"s the default policy. Should use delete_policy instead + with pytest.raises(ClientError) as exc: + client.delete_policy_version( + policyName=policy_name, policyVersionId=policy4["policyVersionId"] + ) + err = exc.value.response["Error"] + err["Message"].should.equal("Cannot delete the default version of a policy") + + +@mock_iot +def test_delete_policy_validation(): + doc = """{ + "Version": "2012-10-17", + "Statement":[ + { + "Effect":"Allow", + "Action":[ + "iot: *" + ], + "Resource":"*" + } + ] + } + """ + client = boto3.client("iot", region_name="ap-northeast-1") + cert = client.create_keys_and_certificate(setAsActive=True) + cert_arn = cert["certificateArn"] + policy_name = "my-policy" + client.create_policy(policyName=policy_name, policyDocument=doc) + client.attach_principal_policy(policyName=policy_name, principal=cert_arn) + + with pytest.raises(ClientError) as e: + client.delete_policy(policyName=policy_name) + e.value.response["Error"]["Message"].should.contain( + "The policy cannot be deleted as the policy is attached to one or more principals (name=%s)" + % policy_name + ) + res = client.list_policies() + res.should.have.key("policies").which.should.have.length_of(1) + + client.detach_principal_policy(policyName=policy_name, principal=cert_arn) + client.delete_policy(policyName=policy_name) + res = client.list_policies() + res.should.have.key("policies").which.should.have.length_of(0) + + +@mock_iot +def test_policy(): + client = boto3.client("iot", region_name="ap-northeast-1") + name = "my-policy" + doc = "{}" + policy = client.create_policy(policyName=name, policyDocument=doc) + policy.should.have.key("policyName").which.should.equal(name) + policy.should.have.key("policyArn").which.should_not.be.none + policy.should.have.key("policyDocument").which.should.equal(doc) + policy.should.have.key("policyVersionId").which.should.equal("1") + + policy = client.get_policy(policyName=name) + policy.should.have.key("policyName").which.should.equal(name) + policy.should.have.key("policyArn").which.should_not.be.none + policy.should.have.key("policyDocument").which.should.equal(doc) + policy.should.have.key("defaultVersionId").which.should.equal("1") + + res = client.list_policies() + res.should.have.key("policies").which.should.have.length_of(1) + for policy in res["policies"]: + policy.should.have.key("policyName").which.should_not.be.none + policy.should.have.key("policyArn").which.should_not.be.none + + client.delete_policy(policyName=name) + res = client.list_policies() + res.should.have.key("policies").which.should.have.length_of(0) diff --git a/tests/test_iot/test_iot_thing_groups.py b/tests/test_iot/test_iot_thing_groups.py new file mode 100644 index 000000000..c49b1a4e1 --- /dev/null +++ b/tests/test_iot/test_iot_thing_groups.py @@ -0,0 +1,567 @@ +import boto3 +import pytest + +from botocore.exceptions import ClientError +from moto import mock_iot + + +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 + + +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") + 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") + 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") + 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 pytest.raises(ClientError) as e: + client.list_thing_groups(parentGroup="inexistant-group-name") + e.value.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") + 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") + 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") + 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") + 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" + tree_dict = { + group_name_1a: {group_name_2a: {},}, + } + generate_thing_group_tree(client, tree_dict) + + # delete group with child + try: + client.delete_thing_group(thingGroupName=group_name_1a) + except client.exceptions.InvalidRequestException as exc: + error_code = exc.response["Error"]["Code"] + error_code.should.equal("InvalidRequestException") + else: + raise Exception("Should have raised error") + + # delete child group + client.delete_thing_group(thingGroupName=group_name_2a) + res = client.list_thing_groups() + res.should.have.key("thingGroups").which.should.have.length_of(1) + res["thingGroups"].should_not.have.key(group_name_2a) + + # now that there is no child group, we can delete the previous group safely + client.delete_thing_group(thingGroupName=group_name_1a) + res = client.list_thing_groups() + res.should.have.key("thingGroups").which.should.have.length_of(0) + + # Deleting an invalid thing group does not raise an error. + res = client.delete_thing_group(thingGroupName="non-existent-group-name") + res["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + + +@mock_iot +def test_describe_thing_group_metadata_hierarchy(): + client = boto3.client("iot", region_name="ap-northeast-1") + 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: {}, + } + group_catalog = generate_thing_group_tree(client, tree_dict) + + # describe groups + # groups level 1 + # 1a + thing_group_description1a = client.describe_thing_group( + thingGroupName=group_name_1a + ) + thing_group_description1a.should.have.key("thingGroupName").which.should.equal( + group_name_1a + ) + thing_group_description1a.should.have.key("thingGroupProperties") + thing_group_description1a.should.have.key("thingGroupMetadata") + thing_group_description1a["thingGroupMetadata"].should.have.key("creationDate") + thing_group_description1a.should.have.key("version") + # 1b + thing_group_description1b = client.describe_thing_group( + thingGroupName=group_name_1b + ) + thing_group_description1b.should.have.key("thingGroupName").which.should.equal( + group_name_1b + ) + thing_group_description1b.should.have.key("thingGroupProperties") + thing_group_description1b.should.have.key("thingGroupMetadata") + thing_group_description1b["thingGroupMetadata"].should.have.length_of(1) + thing_group_description1b["thingGroupMetadata"].should.have.key("creationDate") + thing_group_description1b.should.have.key("version") + # groups level 2 + # 2a + thing_group_description2a = client.describe_thing_group( + thingGroupName=group_name_2a + ) + thing_group_description2a.should.have.key("thingGroupName").which.should.equal( + group_name_2a + ) + thing_group_description2a.should.have.key("thingGroupProperties") + thing_group_description2a.should.have.key("thingGroupMetadata") + thing_group_description2a["thingGroupMetadata"].should.have.length_of(3) + thing_group_description2a["thingGroupMetadata"].should.have.key( + "parentGroupName" + ).being.equal(group_name_1a) + thing_group_description2a["thingGroupMetadata"].should.have.key( + "rootToParentThingGroups" + ) + thing_group_description2a["thingGroupMetadata"][ + "rootToParentThingGroups" + ].should.have.length_of(1) + thing_group_description2a["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupName" + ].should.match(group_name_1a) + thing_group_description2a["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupArn" + ].should.match(group_catalog[group_name_1a]["thingGroupArn"]) + thing_group_description2a.should.have.key("version") + # 2b + thing_group_description2b = client.describe_thing_group( + thingGroupName=group_name_2b + ) + thing_group_description2b.should.have.key("thingGroupName").which.should.equal( + group_name_2b + ) + thing_group_description2b.should.have.key("thingGroupProperties") + thing_group_description2b.should.have.key("thingGroupMetadata") + thing_group_description2b["thingGroupMetadata"].should.have.length_of(3) + thing_group_description2b["thingGroupMetadata"].should.have.key( + "parentGroupName" + ).being.equal(group_name_1a) + thing_group_description2b["thingGroupMetadata"].should.have.key( + "rootToParentThingGroups" + ) + thing_group_description2b["thingGroupMetadata"][ + "rootToParentThingGroups" + ].should.have.length_of(1) + thing_group_description2b["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupName" + ].should.match(group_name_1a) + thing_group_description2b["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupArn" + ].should.match(group_catalog[group_name_1a]["thingGroupArn"]) + thing_group_description2b.should.have.key("version") + # groups level 3 + # 3a + thing_group_description3a = client.describe_thing_group( + thingGroupName=group_name_3a + ) + thing_group_description3a.should.have.key("thingGroupName").which.should.equal( + group_name_3a + ) + thing_group_description3a.should.have.key("thingGroupProperties") + thing_group_description3a.should.have.key("thingGroupMetadata") + thing_group_description3a["thingGroupMetadata"].should.have.length_of(3) + thing_group_description3a["thingGroupMetadata"].should.have.key( + "parentGroupName" + ).being.equal(group_name_2a) + thing_group_description3a["thingGroupMetadata"].should.have.key( + "rootToParentThingGroups" + ) + thing_group_description3a["thingGroupMetadata"][ + "rootToParentThingGroups" + ].should.have.length_of(2) + thing_group_description3a["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupName" + ].should.match(group_name_1a) + thing_group_description3a["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupArn" + ].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(group_catalog[group_name_2a]["thingGroupArn"]) + thing_group_description3a.should.have.key("version") + # 3b + thing_group_description3b = client.describe_thing_group( + thingGroupName=group_name_3b + ) + thing_group_description3b.should.have.key("thingGroupName").which.should.equal( + group_name_3b + ) + thing_group_description3b.should.have.key("thingGroupProperties") + thing_group_description3b.should.have.key("thingGroupMetadata") + thing_group_description3b["thingGroupMetadata"].should.have.length_of(3) + thing_group_description3b["thingGroupMetadata"].should.have.key( + "parentGroupName" + ).being.equal(group_name_2a) + thing_group_description3b["thingGroupMetadata"].should.have.key( + "rootToParentThingGroups" + ) + thing_group_description3b["thingGroupMetadata"][ + "rootToParentThingGroups" + ].should.have.length_of(2) + thing_group_description3b["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupName" + ].should.match(group_name_1a) + thing_group_description3b["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupArn" + ].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(group_catalog[group_name_2a]["thingGroupArn"]) + thing_group_description3b.should.have.key("version") + # 3c + thing_group_description3c = client.describe_thing_group( + thingGroupName=group_name_3c + ) + thing_group_description3c.should.have.key("thingGroupName").which.should.equal( + group_name_3c + ) + thing_group_description3c.should.have.key("thingGroupProperties") + thing_group_description3c.should.have.key("thingGroupMetadata") + thing_group_description3c["thingGroupMetadata"].should.have.length_of(3) + thing_group_description3c["thingGroupMetadata"].should.have.key( + "parentGroupName" + ).being.equal(group_name_2b) + thing_group_description3c["thingGroupMetadata"].should.have.key( + "rootToParentThingGroups" + ) + thing_group_description3c["thingGroupMetadata"][ + "rootToParentThingGroups" + ].should.have.length_of(2) + thing_group_description3c["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupName" + ].should.match(group_name_1a) + thing_group_description3c["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupArn" + ].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(group_catalog[group_name_2b]["thingGroupArn"]) + thing_group_description3c.should.have.key("version") + # 3d + thing_group_description3d = client.describe_thing_group( + thingGroupName=group_name_3d + ) + thing_group_description3d.should.have.key("thingGroupName").which.should.equal( + group_name_3d + ) + thing_group_description3d.should.have.key("thingGroupProperties") + thing_group_description3d.should.have.key("thingGroupMetadata") + thing_group_description3d["thingGroupMetadata"].should.have.length_of(3) + thing_group_description3d["thingGroupMetadata"].should.have.key( + "parentGroupName" + ).being.equal(group_name_2b) + thing_group_description3d["thingGroupMetadata"].should.have.key( + "rootToParentThingGroups" + ) + thing_group_description3d["thingGroupMetadata"][ + "rootToParentThingGroups" + ].should.have.length_of(2) + thing_group_description3d["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupName" + ].should.match(group_name_1a) + thing_group_description3d["thingGroupMetadata"]["rootToParentThingGroups"][0][ + "groupArn" + ].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(group_catalog[group_name_2b]["thingGroupArn"]) + thing_group_description3d.should.have.key("version") + + +@mock_iot +def test_thing_groups(): + client = boto3.client("iot", region_name="ap-northeast-1") + group_name = "my-group-name" + + # thing group + thing_group = client.create_thing_group(thingGroupName=group_name) + thing_group.should.have.key("thingGroupName").which.should.equal(group_name) + thing_group.should.have.key("thingGroupArn") + thing_group["thingGroupArn"].should.contain(group_name) + + res = client.list_thing_groups() + res.should.have.key("thingGroups").which.should.have.length_of(1) + for thing_group in res["thingGroups"]: + thing_group.should.have.key("groupName").which.should_not.be.none + thing_group.should.have.key("groupArn").which.should_not.be.none + + thing_group = client.describe_thing_group(thingGroupName=group_name) + thing_group.should.have.key("thingGroupName").which.should.equal(group_name) + thing_group.should.have.key("thingGroupProperties") + thing_group.should.have.key("thingGroupMetadata") + thing_group.should.have.key("version") + thing_group.should.have.key("thingGroupArn") + thing_group["thingGroupArn"].should.contain(group_name) + + # delete thing group + client.delete_thing_group(thingGroupName=group_name) + res = client.list_thing_groups() + res.should.have.key("thingGroups").which.should.have.length_of(0) + + # props create test + props = { + "thingGroupDescription": "my first thing group", + "attributePayload": {"attributes": {"key1": "val01", "Key02": "VAL2"}}, + } + thing_group = client.create_thing_group( + thingGroupName=group_name, thingGroupProperties=props + ) + thing_group.should.have.key("thingGroupName").which.should.equal(group_name) + thing_group.should.have.key("thingGroupArn") + + thing_group = client.describe_thing_group(thingGroupName=group_name) + thing_group.should.have.key("thingGroupProperties").which.should.have.key( + "attributePayload" + ).which.should.have.key("attributes") + res_props = thing_group["thingGroupProperties"]["attributePayload"]["attributes"] + res_props.should.have.key("key1").which.should.equal("val01") + res_props.should.have.key("Key02").which.should.equal("VAL2") + + # props update test with merge + new_props = {"attributePayload": {"attributes": {"k3": "v3"}, "merge": True}} + client.update_thing_group(thingGroupName=group_name, thingGroupProperties=new_props) + thing_group = client.describe_thing_group(thingGroupName=group_name) + thing_group.should.have.key("thingGroupProperties").which.should.have.key( + "attributePayload" + ).which.should.have.key("attributes") + res_props = thing_group["thingGroupProperties"]["attributePayload"]["attributes"] + res_props.should.have.key("key1").which.should.equal("val01") + res_props.should.have.key("Key02").which.should.equal("VAL2") + + res_props.should.have.key("k3").which.should.equal("v3") + + # props update test + new_props = {"attributePayload": {"attributes": {"k4": "v4"}}} + client.update_thing_group(thingGroupName=group_name, thingGroupProperties=new_props) + thing_group = client.describe_thing_group(thingGroupName=group_name) + thing_group.should.have.key("thingGroupProperties").which.should.have.key( + "attributePayload" + ).which.should.have.key("attributes") + res_props = thing_group["thingGroupProperties"]["attributePayload"]["attributes"] + res_props.should.have.key("k4").which.should.equal("v4") + res_props.should_not.have.key("key1") + + +@mock_iot +def test_thing_group_relations(): + client = boto3.client("iot", region_name="ap-northeast-1") + name = "my-thing" + group_name = "my-group-name" + + # thing group + thing_group = client.create_thing_group(thingGroupName=group_name) + thing_group.should.have.key("thingGroupName").which.should.equal(group_name) + thing_group.should.have.key("thingGroupArn") + + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + # add in 4 way + client.add_thing_to_thing_group(thingGroupName=group_name, thingName=name) + client.add_thing_to_thing_group( + thingGroupArn=thing_group["thingGroupArn"], thingArn=thing["thingArn"] + ) + client.add_thing_to_thing_group( + thingGroupName=group_name, thingArn=thing["thingArn"] + ) + client.add_thing_to_thing_group( + thingGroupArn=thing_group["thingGroupArn"], thingName=name + ) + + things = client.list_things_in_thing_group(thingGroupName=group_name) + things.should.have.key("things") + things["things"].should.have.length_of(1) + + thing_groups = client.list_thing_groups_for_thing(thingName=name) + thing_groups.should.have.key("thingGroups") + thing_groups["thingGroups"].should.have.length_of(1) + + # remove in 4 way + client.remove_thing_from_thing_group(thingGroupName=group_name, thingName=name) + client.remove_thing_from_thing_group( + thingGroupArn=thing_group["thingGroupArn"], thingArn=thing["thingArn"] + ) + client.remove_thing_from_thing_group( + thingGroupName=group_name, thingArn=thing["thingArn"] + ) + client.remove_thing_from_thing_group( + thingGroupArn=thing_group["thingGroupArn"], thingName=name + ) + things = client.list_things_in_thing_group(thingGroupName=group_name) + things.should.have.key("things") + things["things"].should.have.length_of(0) + + # update thing group for thing + client.update_thing_groups_for_thing(thingName=name, thingGroupsToAdd=[group_name]) + things = client.list_things_in_thing_group(thingGroupName=group_name) + things.should.have.key("things") + things["things"].should.have.length_of(1) + + client.update_thing_groups_for_thing( + thingName=name, thingGroupsToRemove=[group_name] + ) + things = client.list_things_in_thing_group(thingGroupName=group_name) + things.should.have.key("things") + things["things"].should.have.length_of(0) diff --git a/tests/test_iot/test_iot_thing_types.py b/tests/test_iot/test_iot_thing_types.py new file mode 100644 index 000000000..01181098a --- /dev/null +++ b/tests/test_iot/test_iot_thing_types.py @@ -0,0 +1,86 @@ +import boto3 + +from moto import mock_iot + + +@mock_iot +def test_create_thing_type(): + client = boto3.client("iot", region_name="ap-northeast-1") + type_name = "my-type-name" + + thing_type = client.create_thing_type(thingTypeName=type_name) + thing_type.should.have.key("thingTypeName").which.should.equal(type_name) + thing_type.should.have.key("thingTypeArn") + thing_type["thingTypeArn"].should.contain(type_name) + + +@mock_iot +def test_describe_thing_type(): + client = boto3.client("iot", region_name="ap-northeast-1") + type_name = "my-type-name" + + client.create_thing_type(thingTypeName=type_name) + + thing_type = client.describe_thing_type(thingTypeName=type_name) + thing_type.should.have.key("thingTypeName").which.should.equal(type_name) + thing_type.should.have.key("thingTypeProperties") + thing_type.should.have.key("thingTypeMetadata") + thing_type.should.have.key("thingTypeArn") + thing_type["thingTypeArn"].should.contain(type_name) + + +@mock_iot +def test_list_thing_types(): + client = boto3.client("iot", region_name="ap-northeast-1") + + for i in range(0, 100): + client.create_thing_type(thingTypeName=str(i + 1)) + + thing_types = client.list_thing_types() + thing_types.should.have.key("nextToken") + thing_types.should.have.key("thingTypes").which.should.have.length_of(50) + thing_types["thingTypes"][0]["thingTypeName"].should.equal("1") + thing_types["thingTypes"][-1]["thingTypeName"].should.equal("50") + + thing_types = client.list_thing_types(nextToken=thing_types["nextToken"]) + thing_types.should.have.key("thingTypes").which.should.have.length_of(50) + thing_types.should_not.have.key("nextToken") + thing_types["thingTypes"][0]["thingTypeName"].should.equal("51") + thing_types["thingTypes"][-1]["thingTypeName"].should.equal("100") + + +@mock_iot +def test_list_thing_types_with_typename_filter(): + client = boto3.client("iot", region_name="ap-northeast-1") + + client.create_thing_type(thingTypeName="thing") + client.create_thing_type(thingTypeName="thingType") + client.create_thing_type(thingTypeName="thingTypeName") + client.create_thing_type(thingTypeName="thingTypeNameGroup") + client.create_thing_type(thingTypeName="shouldNotFind") + client.create_thing_type(thingTypeName="find me it shall not") + + thing_types = client.list_thing_types(thingTypeName="thing") + thing_types.should_not.have.key("nextToken") + thing_types.should.have.key("thingTypes").which.should.have.length_of(4) + thing_types["thingTypes"][0]["thingTypeName"].should.equal("thing") + thing_types["thingTypes"][-1]["thingTypeName"].should.equal("thingTypeNameGroup") + + thing_types = client.list_thing_types(thingTypeName="thingTypeName") + thing_types.should_not.have.key("nextToken") + thing_types.should.have.key("thingTypes").which.should.have.length_of(2) + thing_types["thingTypes"][0]["thingTypeName"].should.equal("thingTypeName") + thing_types["thingTypes"][-1]["thingTypeName"].should.equal("thingTypeNameGroup") + + +@mock_iot +def test_delete_thing_type(): + client = boto3.client("iot", region_name="ap-northeast-1") + type_name = "my-type-name" + + client.create_thing_type(thingTypeName=type_name) + + # delete thing type + client.delete_thing_type(thingTypeName=type_name) + res = client.list_thing_types() + res.should.have.key("thingTypes").which.should.have.length_of(0) diff --git a/tests/test_iot/test_iot_things.py b/tests/test_iot/test_iot_things.py new file mode 100644 index 000000000..5cdcdc069 --- /dev/null +++ b/tests/test_iot/test_iot_things.py @@ -0,0 +1,244 @@ +import boto3 + +from moto import mock_iot +from moto.core import ACCOUNT_ID + + +@mock_iot +def test_create_thing(): + client = boto3.client("iot", region_name="ap-northeast-1") + name = "my-thing" + + thing = client.create_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + res = client.list_things() + res.should.have.key("things").which.should.have.length_of(1) + res["things"][0].should.have.key("thingName").which.should_not.be.none + res["things"][0].should.have.key("thingArn").which.should_not.be.none + + +@mock_iot +def test_create_thing_with_type(): + client = boto3.client("iot", region_name="ap-northeast-1") + name = "my-thing" + type_name = "my-type-name" + + client.create_thing_type(thingTypeName=type_name) + + thing = client.create_thing(thingName=name, thingTypeName=type_name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("thingArn") + + res = client.list_things() + res.should.have.key("things").which.should.have.length_of(1) + res["things"][0].should.have.key("thingName").which.should_not.be.none + res["things"][0].should.have.key("thingArn").which.should_not.be.none + + thing = client.describe_thing(thingName=name) + thing.should.have.key("thingTypeName").equals(type_name) + + +@mock_iot +def test_update_thing(): + client = boto3.client("iot", region_name="ap-northeast-1") + name = "my-thing" + + client.create_thing(thingName=name) + + client.update_thing(thingName=name, attributePayload={"attributes": {"k1": "v1"}}) + res = client.list_things() + res.should.have.key("things").which.should.have.length_of(1) + res["things"][0].should.have.key("thingName").which.should_not.be.none + res["things"][0].should.have.key("thingArn").which.should_not.be.none + res["things"][0]["attributes"].should.have.key("k1").which.should.equal("v1") + + +@mock_iot +def test_describe_thing(): + client = boto3.client("iot", region_name="ap-northeast-1") + name = "my-thing" + + client.create_thing(thingName=name) + client.update_thing(thingName=name, attributePayload={"attributes": {"k1": "v1"}}) + + thing = client.describe_thing(thingName=name) + thing.should.have.key("thingName").which.should.equal(name) + thing.should.have.key("defaultClientId") + thing.should.have.key("attributes").equals({"k1": "v1"}) + thing.should.have.key("version").equals(1) + + +@mock_iot +def test_delete_thing(): + client = boto3.client("iot", region_name="ap-northeast-1") + name = "my-thing" + + client.create_thing(thingName=name) + + # delete thing + client.delete_thing(thingName=name) + + res = client.list_things() + res.should.have.key("things").which.should.have.length_of(0) + + +@mock_iot +def test_list_things_with_next_token(): + client = boto3.client("iot", region_name="ap-northeast-1") + + for i in range(0, 200): + client.create_thing(thingName=str(i + 1)) + + things = client.list_things() + things.should.have.key("nextToken") + things.should.have.key("things").which.should.have.length_of(50) + things["things"][0]["thingName"].should.equal("1") + things["things"][0]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/1" + ) + things["things"][-1]["thingName"].should.equal("50") + things["things"][-1]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/50" + ) + + things = client.list_things(nextToken=things["nextToken"]) + things.should.have.key("nextToken") + things.should.have.key("things").which.should.have.length_of(50) + things["things"][0]["thingName"].should.equal("51") + things["things"][0]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/51" + ) + things["things"][-1]["thingName"].should.equal("100") + things["things"][-1]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/100" + ) + + things = client.list_things(nextToken=things["nextToken"]) + things.should.have.key("nextToken") + things.should.have.key("things").which.should.have.length_of(50) + things["things"][0]["thingName"].should.equal("101") + things["things"][0]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/101" + ) + things["things"][-1]["thingName"].should.equal("150") + things["things"][-1]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/150" + ) + + things = client.list_things(nextToken=things["nextToken"]) + things.should_not.have.key("nextToken") + things.should.have.key("things").which.should.have.length_of(50) + things["things"][0]["thingName"].should.equal("151") + things["things"][0]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/151" + ) + things["things"][-1]["thingName"].should.equal("200") + things["things"][-1]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/200" + ) + + +@mock_iot +def test_list_things_with_attribute_and_thing_type_filter_and_next_token(): + client = boto3.client("iot", region_name="ap-northeast-1") + client.create_thing_type(thingTypeName="my-thing-type") + + for i in range(0, 200): + if not (i + 1) % 3: + attribute_payload = {"attributes": {"foo": "bar"}} + elif not (i + 1) % 5: + attribute_payload = {"attributes": {"bar": "foo"}} + else: + attribute_payload = {} + + if not (i + 1) % 2: + thing_type_name = "my-thing-type" + client.create_thing( + thingName=str(i + 1), + thingTypeName=thing_type_name, + attributePayload=attribute_payload, + ) + else: + client.create_thing( + thingName=str(i + 1), attributePayload=attribute_payload + ) + + # Test filter for thingTypeName + things = client.list_things(thingTypeName=thing_type_name) + things.should.have.key("nextToken") + things.should.have.key("things").which.should.have.length_of(50) + things["things"][0]["thingName"].should.equal("2") + things["things"][0]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/2" + ) + things["things"][-1]["thingName"].should.equal("100") + things["things"][-1]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/100" + ) + all(item["thingTypeName"] == thing_type_name for item in things["things"]) + + things = client.list_things( + nextToken=things["nextToken"], thingTypeName=thing_type_name + ) + things.should_not.have.key("nextToken") + things.should.have.key("things").which.should.have.length_of(50) + things["things"][0]["thingName"].should.equal("102") + things["things"][0]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/102" + ) + things["things"][-1]["thingName"].should.equal("200") + things["things"][-1]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/200" + ) + all(item["thingTypeName"] == thing_type_name for item in things["things"]) + + # Test filter for attributes + things = client.list_things(attributeName="foo", attributeValue="bar") + things.should.have.key("nextToken") + things.should.have.key("things").which.should.have.length_of(50) + things["things"][0]["thingName"].should.equal("3") + things["things"][0]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/3" + ) + things["things"][-1]["thingName"].should.equal("150") + things["things"][-1]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/150" + ) + all(item["attributes"] == {"foo": "bar"} for item in things["things"]) + + things = client.list_things( + nextToken=things["nextToken"], attributeName="foo", attributeValue="bar" + ) + things.should_not.have.key("nextToken") + things.should.have.key("things").which.should.have.length_of(16) + things["things"][0]["thingName"].should.equal("153") + things["things"][0]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/153" + ) + things["things"][-1]["thingName"].should.equal("198") + things["things"][-1]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/198" + ) + all(item["attributes"] == {"foo": "bar"} for item in things["things"]) + + # Test filter for attributes and thingTypeName + things = client.list_things( + thingTypeName=thing_type_name, attributeName="foo", attributeValue="bar" + ) + things.should_not.have.key("nextToken") + things.should.have.key("things").which.should.have.length_of(33) + things["things"][0]["thingName"].should.equal("6") + things["things"][0]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/6" + ) + things["things"][-1]["thingName"].should.equal("198") + things["things"][-1]["thingArn"].should.equal( + f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/198" + ) + all( + item["attributes"] == {"foo": "bar"} + and item["thingTypeName"] == thing_type_name + for item in things["things"] + ) diff --git a/tests/test_iot/test_iot_topic_rules.py b/tests/test_iot/test_iot_topic_rules.py new file mode 100644 index 000000000..0779ce38a --- /dev/null +++ b/tests/test_iot/test_iot_topic_rules.py @@ -0,0 +1,165 @@ +import boto3 +import pytest + +from botocore.exceptions import ClientError +from moto import mock_iot + +name = "my-rule" +payload = { + "sql": "SELECT * FROM 'topic/*' WHERE something > 0", + "actions": [ + {"dynamoDBv2": {"putItem": {"tableName": "my-table"}, "roleArn": "my-role"}} + ], + "errorAction": { + "republish": {"qos": 0, "roleArn": "my-role", "topic": "other-topic"} + }, + "description": "my-description", + "ruleDisabled": False, + "awsIotSqlVersion": "2016-03-23", +} + + +@mock_iot +def test_topic_rule_create(): + client = boto3.client("iot", region_name="ap-northeast-1") + + client.create_topic_rule(ruleName=name, topicRulePayload=payload) + + # duplicated rule name + with pytest.raises(ClientError) as ex: + client.create_topic_rule(ruleName=name, topicRulePayload=payload) + error_code = ex.value.response["Error"]["Code"] + error_code.should.equal("ResourceAlreadyExistsException") + + +@mock_iot +def test_topic_rule_list(): + client = boto3.client("iot", region_name="ap-northeast-1") + + # empty response + res = client.list_topic_rules() + res.should.have.key("rules").which.should.have.length_of(0) + + client.create_topic_rule(ruleName=name, topicRulePayload=payload) + client.create_topic_rule(ruleName="my-rule-2", topicRulePayload=payload) + + res = client.list_topic_rules() + res.should.have.key("rules").which.should.have.length_of(2) + for rule, rule_name in zip(res["rules"], [name, "my-rule-2"]): + rule.should.have.key("ruleName").which.should.equal(rule_name) + rule.should.have.key("createdAt").which.should_not.be.none + rule.should.have.key("ruleArn").which.should_not.be.none + rule.should.have.key("ruleDisabled").which.should.equal(payload["ruleDisabled"]) + rule.should.have.key("topicPattern").which.should.equal("topic/*") + + +@mock_iot +def test_topic_rule_get(): + client = boto3.client("iot", region_name="ap-northeast-1") + + # no such rule + with pytest.raises(ClientError) as ex: + client.get_topic_rule(ruleName=name) + error_code = ex.value.response["Error"]["Code"] + error_code.should.equal("ResourceNotFoundException") + + client.create_topic_rule(ruleName=name, topicRulePayload=payload) + + rule = client.get_topic_rule(ruleName=name) + + rule.should.have.key("ruleArn").which.should_not.be.none + rule.should.have.key("rule") + rrule = rule["rule"] + rrule.should.have.key("actions").which.should.equal(payload["actions"]) + rrule.should.have.key("awsIotSqlVersion").which.should.equal( + payload["awsIotSqlVersion"] + ) + rrule.should.have.key("createdAt").which.should_not.be.none + rrule.should.have.key("description").which.should.equal(payload["description"]) + rrule.should.have.key("errorAction").which.should.equal(payload["errorAction"]) + rrule.should.have.key("ruleDisabled").which.should.equal(payload["ruleDisabled"]) + rrule.should.have.key("ruleName").which.should.equal(name) + rrule.should.have.key("sql").which.should.equal(payload["sql"]) + + +@mock_iot +def test_topic_rule_replace(): + client = boto3.client("iot", region_name="ap-northeast-1") + + # no such rule + with pytest.raises(ClientError) as ex: + client.replace_topic_rule(ruleName=name, topicRulePayload=payload) + error_code = ex.value.response["Error"]["Code"] + error_code.should.equal("ResourceNotFoundException") + + client.create_topic_rule(ruleName=name, topicRulePayload=payload) + + my_payload = payload.copy() + my_payload["description"] = "new-description" + client.replace_topic_rule( + ruleName=name, topicRulePayload=my_payload, + ) + + rule = client.get_topic_rule(ruleName=name) + rule["rule"]["ruleName"].should.equal(name) + rule["rule"]["description"].should.equal(my_payload["description"]) + + +@mock_iot +def test_topic_rule_disable(): + client = boto3.client("iot", region_name="ap-northeast-1") + + # no such rule + with pytest.raises(ClientError) as ex: + client.disable_topic_rule(ruleName=name) + error_code = ex.value.response["Error"]["Code"] + error_code.should.equal("ResourceNotFoundException") + + client.create_topic_rule(ruleName=name, topicRulePayload=payload) + + client.disable_topic_rule(ruleName=name) + + rule = client.get_topic_rule(ruleName=name) + rule["rule"]["ruleName"].should.equal(name) + rule["rule"]["ruleDisabled"].should.equal(True) + + +@mock_iot +def test_topic_rule_enable(): + client = boto3.client("iot", region_name="ap-northeast-1") + + # no such rule + with pytest.raises(ClientError) as ex: + client.enable_topic_rule(ruleName=name) + error_code = ex.value.response["Error"]["Code"] + error_code.should.equal("ResourceNotFoundException") + + my_payload = payload.copy() + my_payload["ruleDisabled"] = True + client.create_topic_rule(ruleName=name, topicRulePayload=my_payload) + + client.enable_topic_rule(ruleName=name) + + rule = client.get_topic_rule(ruleName=name) + rule["rule"]["ruleName"].should.equal(name) + rule["rule"]["ruleDisabled"].should.equal(False) + + +@mock_iot +def test_topic_rule_delete(): + client = boto3.client("iot", region_name="ap-northeast-1") + + # no such rule + with pytest.raises(ClientError) as ex: + client.delete_topic_rule(ruleName=name) + error_code = ex.value.response["Error"]["Code"] + error_code.should.equal("ResourceNotFoundException") + + client.create_topic_rule(ruleName=name, topicRulePayload=payload) + + client.enable_topic_rule(ruleName=name) + + client.delete_topic_rule(ruleName=name) + + res = client.list_topic_rules() + res.should.have.key("rules").which.should.have.length_of(0)