From 0b588aec89d601352d45ff015c70fbf0e28063e9 Mon Sep 17 00:00:00 2001
From: Frrist <forrest@protocol.ai>
Date: Tue, 22 Jun 2021 11:33:18 -0700
Subject: [PATCH] fix(diff): handle edge case diffing empty against non-empty
 (#56)

---
 diff.go      |  7 ++++++
 diff_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/diff.go b/diff.go
index 0447d7e..105f938 100644
--- a/diff.go
+++ b/diff.go
@@ -67,6 +67,13 @@ func Diff(ctx context.Context, prevBs, curBs cbor.IpldStore, prev, cur cid.Cid,
 		height:   curAmt.height,
 	}
 
+	// edge case of diffing an empty AMT against non-empty
+	if prevAmt.count == 0 && curAmt.count != 0 {
+		return addAll(ctx, curCtx, curAmt.node, 0)
+	}
+	if prevAmt.count != 0 && curAmt.count == 0 {
+		return removeAll(ctx, prevCtx, prevAmt.node, 0)
+	}
 	return diffNode(ctx, prevCtx, curCtx, prevAmt.node, curAmt.node, 0)
 }
 
diff --git a/diff_test.go b/diff_test.go
index e066688..723b325 100644
--- a/diff_test.go
+++ b/diff_test.go
@@ -6,9 +6,8 @@ import (
 	"strconv"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-
 	cbor "github.com/ipfs/go-ipld-cbor"
+	"github.com/stretchr/testify/assert"
 )
 
 type expectedChange struct {
@@ -102,6 +101,65 @@ func TestSimpleAdd(t *testing.T) {
 	ec.assertExpectation(t, cs[0])
 }
 
+func TestDiffEmptyStateWithNonEmptyState(t *testing.T) {
+	t.Run("Removed values", func(t *testing.T) {
+		prevBs := cbor.NewCborStore(newMockBlocks())
+		curBs := cbor.NewCborStore(newMockBlocks())
+		ctx := context.Background()
+
+		prev, err := NewAMT(prevBs)
+		assert.NoError(t, err)
+
+		cur, err := NewAMT(curBs)
+		assert.NoError(t, err)
+		assertCount(t, cur, 0)
+
+		assertSet(t, prev, 2, "foo")
+		assertGet(ctx, t, prev, 2, "foo")
+		assertCount(t, prev, 1)
+
+		cs := diffAndAssertLength(ctx, t, prevBs, curBs, prev, cur, 1)
+
+		ec := expectedChange{
+			Type:   Remove,
+			Key:    2,
+			Before: "foo",
+			After:  "",
+		}
+
+		ec.assertExpectation(t, cs[0])
+	})
+
+	t.Run("Added values", func(t *testing.T) {
+		prevBs := cbor.NewCborStore(newMockBlocks())
+		curBs := cbor.NewCborStore(newMockBlocks())
+		ctx := context.Background()
+
+		prev, err := NewAMT(prevBs)
+		assert.NoError(t, err)
+		assertCount(t, prev, 0)
+
+		cur, err := NewAMT(curBs)
+		assert.NoError(t, err)
+
+		assertSet(t, cur, 2, "foo")
+		assertGet(ctx, t, cur, 2, "foo")
+		assertCount(t, cur, 1)
+
+		cs := diffAndAssertLength(ctx, t, prevBs, curBs, prev, cur, 1)
+
+		ec := expectedChange{
+			Type:   Add,
+			Key:    2,
+			Before: "",
+			After:  "foo",
+		}
+
+		ec.assertExpectation(t, cs[0])
+
+	})
+}
+
 func TestSimpleRemove(t *testing.T) {
 	prevBs := cbor.NewCborStore(newMockBlocks())
 	curBs := cbor.NewCborStore(newMockBlocks())