Repository: seqsense/aws-iot-device-sdk-go
Pull request #238 (2021-03-16 02:08:03)

shadow: reconstruct Desired from Delta

codecov[bot]: # [Codecov](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238?src=pr&el=h1) Report
> Merging [#238](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238?src=pr&el=desc) (816da64) into [master](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/commit/39e80816fd9e0e54396de06f727afa9387ed1ddb?el=desc) (39e8081) will **increase** coverage by `0.29%`.
> The diff coverage is `83.33%`.

[![Impacted file tree graph](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238/graphs/tree.svg?width=650&height=150&src=pr&token=St5U6jzMqv)](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #238      +/-   ##
==========================================
+ Coverage   60.41%   60.71%   +0.29%     
==========================================
  Files          29       29              
  Lines        1483     1489       +6     
==========================================
+ Hits          896      904       +8     
+ Misses        505      502       -3     
- Partials       82       83       +1     
```


| [Impacted Files](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [shadow/state.go](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238/diff?src=pr&el=tree#diff-c2hhZG93L3N0YXRlLmdv) | `65.38% <83.33%> (+1.09%)` | :arrow_up: |
| [shadow/shadow.go](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238/diff?src=pr&el=tree#diff-c2hhZG93L3NoYWRvdy5nbw==) | `78.64% <0.00%> (+1.06%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238?src=pr&el=footer). Last update [39e8081...816da64](https://codecov.io/gh/seqsense/aws-iot-device-sdk-go/pull/238?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/examples/go.sum b/examples/go.sum
index 1f9e020..ed352ef 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/at-wat/mqtt-go v0.12.1 h1:bO211unVuC9EN6BdkyKVislVSoIhVsXC0c57RUQlMJk=
 github.com/at-wat/mqtt-go v0.12.1/go.mod h1:b4QTk+HgoReGBw9R652AH0Kqc2OvkLSD7lOgyxWgeQE=
-github.com/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
-github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go v1.37.33 h1:bAzhEFOh/UKC2e1QwlUA4klDc6EdV0j0AcnzgeGzifU=
+github.com/aws/aws-sdk-go v1.37.33/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
diff --git a/go.mod b/go.mod
index 80e6cdb..9e7db75 100644
--- a/go.mod
+++ b/go.mod
@@ -2,7 +2,7 @@ module github.com/seqsense/aws-iot-device-sdk-go/v5
 
 require (
 	github.com/at-wat/mqtt-go v0.12.1
-	github.com/aws/aws-sdk-go v1.37.30
+	github.com/aws/aws-sdk-go v1.37.33
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/golang/protobuf v1.4.3
 	github.com/google/uuid v1.2.0
diff --git a/go.sum b/go.sum
index 25c896c..537a90b 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/at-wat/mqtt-go v0.12.1 h1:bO211unVuC9EN6BdkyKVislVSoIhVsXC0c57RUQlMJk=
 github.com/at-wat/mqtt-go v0.12.1/go.mod h1:b4QTk+HgoReGBw9R652AH0Kqc2OvkLSD7lOgyxWgeQE=
-github.com/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
-github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go v1.37.33 h1:bAzhEFOh/UKC2e1QwlUA4klDc6EdV0j0AcnzgeGzifU=
+github.com/aws/aws-sdk-go v1.37.33/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
diff --git a/shadow/state.go b/shadow/state.go
index 1fff495..6effaf3 100644
--- a/shadow/state.go
+++ b/shadow/state.go
@@ -62,6 +62,8 @@ type ThingDocument struct {
 	Version     int                `json:"version,omitempty"`
 	Timestamp   int                `json:"timestamp,omitempty"`
 	ClientToken string             `json:"clientToken,omitempty"`
+
+	MaybeIncomplete bool `json:"-"`
 }
 
 type thingStateRaw struct {
@@ -128,10 +130,20 @@ func (s *ThingDocument) updateDelta(state *thingDelta) bool {
 		// Received an old version; just ignore it.
 		return false
 	}
+
+	if s.Version+1 != state.Version {
+		// Versions were dropped.
+		s.MaybeIncomplete = true
+	}
+
 	s.Version = state.Version
 	s.Timestamp = state.Timestamp
 	s.State.Delta = state.State
 	s.Metadata.Delta = state.Metadata
+
+	updateState(s.State.Desired, state.State)
+	updateState(s.Metadata.Desired, state.Metadata)
+
 	return true
 }
 
@@ -156,7 +168,8 @@ func updateStateRaw(state NestedState, update json.RawMessage) error {
 	if err := json.Unmarshal([]byte(update), &u); err != nil {
 		return ioterr.New(err, "unmarshaling update")
 	}
-	return updateState(state, u)
+	updateState(state, u)
+	return nil
 }
 
 func updateStateMetadataRaw(state NestedMetadata, update json.RawMessage) error {
@@ -167,23 +180,23 @@ func updateStateMetadataRaw(state NestedMetadata, update json.RawMessage) error
 	if err := json.Unmarshal([]byte(update), &u); err != nil {
 		return ioterr.New(err, "unmarshaling update")
 	}
-	return updateState(state, u)
+	updateState(state, u)
+	return nil
 }
 
-func updateState(state map[string]interface{}, update map[string]interface{}) error {
+func updateState(state map[string]interface{}, update map[string]interface{}) {
 	if len(update) == 0 {
 		for k := range state {
 			delete(state, k)
 		}
-		return nil
+		return
 	}
 	for key, val := range update {
 		switch v := val.(type) {
 		case NestedState:
 			if s, ok := state[key].(NestedState); ok {
-				if err := updateState(s, v); err != nil {
-					return ioterr.New(err, "updating state")
-				}
+				updateState(s, v)
+				return
 			} else {
 				state[key] = v
 			}
@@ -195,7 +208,7 @@ func updateState(state map[string]interface{}, update map[string]interface{}) er
 			state[key] = v
 		}
 	}
-	return nil
+	return
 }
 
 func hasUpdate(s json.RawMessage) bool {


Test patch:
diff --git a/shadow/shadow_test.go b/shadow/shadow_test.go
index 0ebefe2..8ecbcd6 100644
--- a/shadow/shadow_test.go
+++ b/shadow/shadow_test.go
@@ -18,6 +18,7 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
+	"fmt"
 	"reflect"
 	"testing"
 	"time"
@@ -436,39 +437,158 @@ func TestOnDelta(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	s.(*shadow).doc.Version = 9
 	cli.Handle(s)
 
-	expected := NestedState{
-		"key": "value",
+	testSeq := []struct {
+		expectedDelta NestedState
+		req           *thingDelta
+		expectedDoc   *ThingDocument
+	}{
+		{
+			expectedDelta: NestedState{
+				"key": "value",
+			},
+			req: &thingDelta{
+				State: NestedState{
+					"key": "value",
+				},
+				Metadata: NestedMetadata{
+					"key": Metadata{Timestamp: 1},
+				},
+				Version:   10,
+				Timestamp: 1,
+			},
+			expectedDoc: &ThingDocument{
+				State: ThingState{
+					Desired:  NestedState{"key": "value"},
+					Delta:    NestedState{"key": "value"},
+					Reported: NestedState{},
+				},
+				Metadata: ThingStateMetadata{
+					Desired:  NestedMetadata{"key": Metadata{Timestamp: 1}},
+					Delta:    NestedMetadata{"key": Metadata{Timestamp: 1}},
+					Reported: NestedMetadata{},
+				},
+				Version:   10,
+				Timestamp: 1,
+			},
+		},
+		{
+			expectedDelta: NestedState{
+				"key2": "value2",
+			},
+			req: &thingDelta{
+				State: NestedState{
+					"key2": "value2",
+				},
+				Metadata: NestedMetadata{
+					"key2": Metadata{Timestamp: 2},
+				},
+				Version:   11,
+				Timestamp: 2,
+			},
+			expectedDoc: &ThingDocument{
+				State: ThingState{
+					Desired: NestedState{
+						"key":  "value",
+						"key2": "value2",
+					},
+					Delta: NestedState{
+						"key2": "value2",
+					},
+					Reported: NestedState{},
+				},
+				Metadata: ThingStateMetadata{
+					Desired: NestedMetadata{
+						"key":  Metadata{Timestamp: 1},
+						"key2": Metadata{Timestamp: 2},
+					},
+					Delta: NestedMetadata{
+						"key2": Metadata{Timestamp: 2},
+					},
+					Reported: NestedMetadata{},
+				},
+				Version:   11,
+				Timestamp: 2,
+			},
+		},
+		{
+			expectedDelta: NestedState{
+				"key": "value3",
+			},
+			req: &thingDelta{
+				State: NestedState{
+					"key": "value3",
+				},
+				Metadata: NestedMetadata{
+					"key": Metadata{Timestamp: 4},
+				},
+				Version:   13,
+				Timestamp: 4,
+			},
+			expectedDoc: &ThingDocument{
+				State: ThingState{
+					Desired: NestedState{
+						"key":  "value3",
+						"key2": "value2",
+					},
+					Delta: NestedState{
+						"key": "value3",
+					},
+					Reported: NestedState{},
+				},
+				Metadata: ThingStateMetadata{
+					Desired: NestedMetadata{
+						"key":  Metadata{Timestamp: 4},
+						"key2": Metadata{Timestamp: 2},
+					},
+					Delta: NestedMetadata{
+						"key": Metadata{Timestamp: 4},
+					},
+					Reported: NestedMetadata{},
+				},
+				Version:         13,
+				Timestamp:       4,
+				MaybeIncomplete: true,
+			},
+		},
 	}
 
-	done := make(chan struct{})
-	s.OnDelta(func(delta NestedState) {
-		if !reflect.DeepEqual(expected, delta) {
-			t.Fatalf("Expected delta:\n%+v\ngot:\n%+v", expected, delta)
-		}
-		close(done)
-	})
+	for i, seq := range testSeq {
+		expectedDelta := seq.expectedDelta
+		req := seq.req
+		expectedDoc := seq.expectedDoc
 
-	req := &thingDelta{
-		State: NestedState{
-			"key": "value",
-		},
-		Version: 10,
-	}
-	breq, err := json.Marshal(req)
-	if err != nil {
-		t.Fatal(err)
-	}
-	cli.Serve(&mqtt.Message{
-		Topic:   s.(*shadow).topic("update/delta"),
-		Payload: breq,
-	})
+		t.Run(fmt.Sprintf("Seq%d", i), func(t *testing.T) {
+			done := make(chan struct{})
+			s.OnDelta(func(delta NestedState) {
+				if !reflect.DeepEqual(expectedDelta, delta) {
+					t.Fatalf("Expected delta:\n%+v\ngot:\n%+v", expectedDelta, delta)
+				}
+				close(done)
+			})
 
-	select {
-	case <-done:
-	case <-ctx.Done():
-		t.Fatal("Timeout")
+			breq, err := json.Marshal(req)
+			if err != nil {
+				t.Fatal(err)
+			}
+			cli.Serve(&mqtt.Message{
+				Topic:   s.(*shadow).topic("update/delta"),
+				Payload: breq,
+			})
+
+			select {
+			case <-done:
+			case <-ctx.Done():
+				t.Fatal("Timeout")
+			}
+
+			doc := s.Document()
+			if !reflect.DeepEqual(expectedDoc, doc) {
+				t.Errorf("Expected document:\n%+v\ngot:\n%+v", expectedDoc, doc)
+			}
+		})
 	}
 }