Repository: getmoto/moto

Issue:
IoT - thingId not present after create_thing()
```
import boto3
from moto import mock_iot

@mock_iot
def test_create_thing():
    client = boto3.client("iot", region_name="ap-northeast-1")

    thing = client.create_thing(
        thingName="IDontHaveAThingId"
    )

    assert thing["thingName"] == "IDontHaveAThingId"
    assert "thingId" in thing  # fails

    # other confirming ways to look for thingId:

    # response = client.describe_thing(
    #                 thingName="IDontHaveAThingId"
    #             )
    # assert response["thingId"] is not None  # fails

    # res = client.list_things()
    # assert res["things"][0]["thingId"] is not None  # fails
```


Expected:  thingId to be present after create_thing() runs.  In boto3 there are 3 fields returned on create_thing(), thingName, thingArn, and thingId.  In moto only 2 are returned

What actually happens: no thingId present
Moto version: 4.2.10
Installed via: pip install moto==4.2.10
Python version: v3.10.12




Discussion:
code above cobbled up from /tests/test_iot/test_iot_things.py
could add `assert "thingId" in thing` on L#15 
Thanks for raising this @mikedambrogia - looks like we don't deal with `thing_id` at all at the moment. Marking it as an enhancement.

Out of interest, do you know the format that AWS uses for `thing_id`s? Are they UUID's, or something else? I couldn't find any documentation on that.
@bblommers 

it's a UUID

to accurately mock a thing, the thingId needs to be present.  And there is no method to add/edit the immutable value. The other properties of the thing are present (arn, attributes, version, etc)  

In case someone else is looking for a hacky workaround I used to get my test to pass:

```
client = boto3.client('iot')

response = client.describe_thing(
    thingName=device
)

thing = {
    "defaultClientId": response["defaultClientId"],
    "thingName": response["thingName"],
    "thingId":  "1234" if "thingId" not in response.keys() else response["thingId"],  # @todo workaround for moto bug 7077 where thingId is not created  # noqa
    "thingArn": response["thingArn"],
    "attributes": response["attributes"],
    "version": response["version"]
    }
```



Patch:
diff --git a/moto/iot/models.py b/moto/iot/models.py
--- a/moto/iot/models.py
+++ b/moto/iot/models.py
@@ -43,6 +43,7 @@ def __init__(
         region_name: str,
     ):
         self.region_name = region_name
+        self.thing_id = str(random.uuid4())
         self.thing_name = thing_name
         self.thing_type = thing_type
         self.attributes = attributes
@@ -68,6 +69,7 @@ def to_dict(
         self,
         include_default_client_id: bool = False,
         include_connectivity: bool = False,
+        include_thing_id: bool = False,
     ) -> Dict[str, Any]:
         obj = {
             "thingName": self.thing_name,
@@ -84,6 +86,8 @@ def to_dict(
                 "connected": True,
                 "timestamp": time.mktime(utcnow().timetuple()),
             }
+        if include_thing_id:
+            obj["thingId"] = self.thing_id
         return obj
 
 
@@ -697,7 +701,7 @@ def create_thing(
         thing_name: str,
         thing_type_name: str,
         attribute_payload: Optional[Dict[str, Any]],
-    ) -> Tuple[str, str]:
+    ) -> FakeThing:
         thing_types = self.list_thing_types()
         thing_type = None
         if thing_type_name:
@@ -723,7 +727,7 @@ def create_thing(
             thing_name, thing_type, attributes, self.account_id, self.region_name
         )
         self.things[thing.arn] = thing
-        return thing.thing_name, thing.arn
+        return thing
 
     def create_thing_type(
         self, thing_type_name: str, thing_type_properties: Dict[str, Any]
@@ -1128,9 +1132,15 @@ def detach_policy(self, policy_name: str, target: str) -> None:
         del self.principal_policies[k]
 
     def list_attached_policies(self, target: str) -> List[FakePolicy]:
+        """
+        Pagination is not yet implemented
+        """
         return [v[1] for k, v in self.principal_policies.items() if k[0] == target]
 
     def list_policies(self) -> Iterable[FakePolicy]:
+        """
+        Pagination is not yet implemented
+        """
         return self.policies.values()
 
     def get_policy(self, policy_name: str) -> FakePolicy:
@@ -1273,12 +1283,18 @@ def detach_principal_policy(self, policy_name: str, principal_arn: str) -> None:
         del self.principal_policies[k]
 
     def list_principal_policies(self, principal_arn: str) -> List[FakePolicy]:
+        """
+        Pagination is not yet implemented
+        """
         policies = [
             v[1] for k, v in self.principal_policies.items() if k[0] == principal_arn
         ]
         return policies
 
     def list_policy_principals(self, policy_name: str) -> List[str]:
+        """
+        Pagination is not yet implemented
+        """
         # this action is deprecated
         # https://docs.aws.amazon.com/iot/latest/apireference/API_ListTargetsForPolicy.html
         # should use ListTargetsForPolicy instead
@@ -1288,6 +1304,9 @@ def list_policy_principals(self, policy_name: str) -> List[str]:
         return principals
 
     def list_targets_for_policy(self, policy_name: str) -> List[str]:
+        """
+        Pagination is not yet implemented
+        """
         # This behaviour is different to list_policy_principals which will just return an empty list
         if policy_name not in self.policies:
             raise ResourceNotFoundException("Policy not found")
diff --git a/moto/iot/responses.py b/moto/iot/responses.py
--- a/moto/iot/responses.py
+++ b/moto/iot/responses.py
@@ -34,12 +34,14 @@ def create_thing(self) -> str:
         thing_name = self._get_param("thingName")
         thing_type_name = self._get_param("thingTypeName")
         attribute_payload = self._get_param("attributePayload")
-        thing_name, thing_arn = self.iot_backend.create_thing(
+        thing = self.iot_backend.create_thing(
             thing_name=thing_name,
             thing_type_name=thing_type_name,
             attribute_payload=attribute_payload,
         )
-        return json.dumps(dict(thingName=thing_name, thingArn=thing_arn))
+        return json.dumps(
+            dict(thingName=thing.thing_name, thingArn=thing.arn, thingId=thing.thing_id)
+        )
 
     def create_thing_type(self) -> str:
         thing_type_name = self._get_param("thingTypeName")
@@ -95,7 +97,9 @@ def list_things(self) -> str:
     def describe_thing(self) -> str:
         thing_name = self._get_param("thingName")
         thing = self.iot_backend.describe_thing(thing_name=thing_name)
-        return json.dumps(thing.to_dict(include_default_client_id=True))
+        return json.dumps(
+            thing.to_dict(include_default_client_id=True, include_thing_id=True)
+        )
 
     def describe_thing_type(self) -> str:
         thing_type_name = self._get_param("thingTypeName")
@@ -415,12 +419,8 @@ def create_policy(self) -> str:
         return json.dumps(policy.to_dict_at_creation())
 
     def list_policies(self) -> str:
-        # marker = self._get_param("marker")
-        # page_size = self._get_int_param("pageSize")
-        # ascending_order = self._get_param("ascendingOrder")
         policies = self.iot_backend.list_policies()
 
-        # TODO: implement pagination in the future
         return json.dumps(dict(policies=[_.to_dict() for _ in policies]))
 
     def get_policy(self) -> str:
@@ -492,14 +492,8 @@ def dispatch_attached_policies(
 
     def list_attached_policies(self) -> str:
         principal = self._get_param("target")
-        # marker = self._get_param("marker")
-        # page_size = self._get_int_param("pageSize")
         policies = self.iot_backend.list_attached_policies(target=principal)
-        # TODO: implement pagination in the future
-        next_marker = None
-        return json.dumps(
-            dict(policies=[_.to_dict() for _ in policies], nextMarker=next_marker)
-        )
+        return json.dumps(dict(policies=[_.to_dict() for _ in policies]))
 
     def attach_principal_policy(self) -> str:
         policy_name = self._get_param("policyName")
@@ -525,31 +519,19 @@ def detach_principal_policy(self) -> str:
 
     def list_principal_policies(self) -> str:
         principal = self.headers.get("x-amzn-iot-principal")
-        # marker = self._get_param("marker")
-        # page_size = self._get_int_param("pageSize")
-        # ascending_order = self._get_param("ascendingOrder")
         policies = self.iot_backend.list_principal_policies(principal_arn=principal)
-        # TODO: implement pagination in the future
-        next_marker = None
-        return json.dumps(
-            dict(policies=[_.to_dict() for _ in policies], nextMarker=next_marker)
-        )
+        return json.dumps(dict(policies=[_.to_dict() for _ in policies]))
 
     def list_policy_principals(self) -> str:
         policy_name = self.headers.get("x-amzn-iot-policy")
-        # marker = self._get_param("marker")
-        # page_size = self._get_int_param("pageSize")
-        # ascending_order = self._get_param("ascendingOrder")
         principals = self.iot_backend.list_policy_principals(policy_name=policy_name)
-        # TODO: implement pagination in the future
-        next_marker = None
-        return json.dumps(dict(principals=principals, nextMarker=next_marker))
+        return json.dumps(dict(principals=principals))
 
     def list_targets_for_policy(self) -> str:
         """https://docs.aws.amazon.com/iot/latest/apireference/API_ListTargetsForPolicy.html"""
         policy_name = self._get_param("policyName")
         principals = self.iot_backend.list_targets_for_policy(policy_name=policy_name)
-        return json.dumps(dict(targets=principals, nextMarker=None))
+        return json.dumps(dict(targets=principals))
 
     def attach_thing_principal(self) -> str:
         thing_name = self._get_param("thingName")


Test patch:
diff --git a/tests/test_iot/test_iot_things.py b/tests/test_iot/test_iot_things.py
--- a/tests/test_iot/test_iot_things.py
+++ b/tests/test_iot/test_iot_things.py
@@ -11,12 +11,13 @@ def test_create_thing():
 
     thing = client.create_thing(thingName=name)
     assert thing["thingName"] == name
-    assert "thingArn" in thing
+    assert thing["thingArn"] is not None
+    assert thing["thingId"] is not None
 
     res = client.list_things()
     assert len(res["things"]) == 1
-    assert res["things"][0]["thingName"] is not None
-    assert res["things"][0]["thingArn"] is not None
+    assert res["things"][0]["thingName"] == name
+    assert res["things"][0]["thingArn"] == thing["thingArn"]
 
 
 @mock_iot
@@ -105,6 +106,7 @@ def test_describe_thing():
     client.update_thing(thingName=name, attributePayload={"attributes": {"k1": "v1"}})
 
     thing = client.describe_thing(thingName=name)
+    assert thing["thingId"] is not None
     assert thing["thingName"] == name
     assert "defaultClientId" in thing
     assert thing["attributes"] == {"k1": "v1"}