diff --git a/moto/iotdata/exceptions.py b/moto/iotdata/exceptions.py index ddc6b37fd..f2c209eed 100644 --- a/moto/iotdata/exceptions.py +++ b/moto/iotdata/exceptions.py @@ -21,3 +21,11 @@ class InvalidRequestException(IoTDataPlaneClientError): super(InvalidRequestException, self).__init__( "InvalidRequestException", message ) + + +class ConflictException(IoTDataPlaneClientError): + def __init__(self, message): + self.code = 409 + super(ConflictException, self).__init__( + "ConflictException", message + ) diff --git a/moto/iotdata/models.py b/moto/iotdata/models.py index ad4caa89e..fec066f07 100644 --- a/moto/iotdata/models.py +++ b/moto/iotdata/models.py @@ -6,6 +6,7 @@ import jsondiff from moto.core import BaseBackend, BaseModel from moto.iot import iot_backends from .exceptions import ( + ConflictException, ResourceNotFoundException, InvalidRequestException ) @@ -161,6 +162,8 @@ class IoTDataPlaneBackend(BaseBackend): if any(_ for _ in payload['state'].keys() if _ not in ['desired', 'reported']): raise InvalidRequestException('State contains an invalid node') + if 'version' in payload and thing.thing_shadow.version != payload['version']: + raise ConflictException('Version conflict') new_shadow = FakeShadow.create_from_previous_version(thing.thing_shadow, payload) thing.thing_shadow = new_shadow return thing.thing_shadow diff --git a/tests/test_iotdata/test_iotdata.py b/tests/test_iotdata/test_iotdata.py index 09c1ada4c..1cedcaa72 100644 --- a/tests/test_iotdata/test_iotdata.py +++ b/tests/test_iotdata/test_iotdata.py @@ -86,6 +86,12 @@ def test_update(): payload.should.have.key('version').which.should.equal(2) payload.should.have.key('timestamp') + raw_payload = b'{"state": {"desired": {"led": "on"}}, "version": 1}' + with assert_raises(ClientError) as ex: + client.update_thing_shadow(thingName=name, payload=raw_payload) + ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(409) + ex.exception.response['Error']['Message'].should.equal('Version conflict') + @mock_iotdata def test_publish():