update_test.go

  1package ygo_test
  2
  3import (
  4	"git.kilimanjaro.io/ygo"
  5	"testing"
  6)
  7
  8func TestUpdateExchange(t *testing.T) {
  9	// Create two documents
 10	d1, err := ygo.NewDocWithOptions(ygo.DocOptions{ClientID: 1})
 11	if err != nil {
 12		t.Fatalf("failed to create doc1: %v", err)
 13	}
 14	defer d1.Destroy()
 15
 16	d2, err := ygo.NewDocWithOptions(ygo.DocOptions{ClientID: 2})
 17	if err != nil {
 18		t.Fatalf("failed to create doc2: %v", err)
 19	}
 20	defer d2.Destroy()
 21
 22	// Create text on both
 23	txt1, err := d1.GetText("test")
 24	if err != nil {
 25		t.Fatalf("failed to get text1: %v", err)
 26	}
 27	defer txt1.Destroy()
 28
 29	txt2, err := d2.GetText("test")
 30	if err != nil {
 31		t.Fatalf("failed to get text2: %v", err)
 32	}
 33	defer txt2.Destroy()
 34
 35	// Variables to capture state between transactions
 36	var sv1, sv2 *ygo.StateVector
 37	var diff1, diff2 *ygo.Update
 38
 39	// First transaction on d1: insert text and get state vector
 40	err = d1.WithWriteTransaction(func(txn1 *ygo.Transaction) error {
 41		txt1.Insert(txn1, 0, "world")
 42		sv1 = txn1.GetStateVector()
 43		if sv1 == nil {
 44			t.Fatal("failed to get state vector 1")
 45		}
 46		return nil
 47	})
 48	if err != nil {
 49		t.Fatalf("failed d1 first transaction: %v", err)
 50	}
 51
 52	// Second transaction on d2: insert text and get state vector
 53	err = d2.WithWriteTransaction(func(txn2 *ygo.Transaction) error {
 54		txt2.Insert(txn2, 0, "hello ")
 55		sv2 = txn2.GetStateVector()
 56		if sv2 == nil {
 57			t.Fatal("failed to get state vector 2")
 58		}
 59		return nil
 60	})
 61	if err != nil {
 62		t.Fatalf("failed d2 first transaction: %v", err)
 63	}
 64
 65	// Third transaction on d1: calculate diff using sv2
 66	err = d1.WithWriteTransaction(func(txn1 *ygo.Transaction) error {
 67		diff1 = txn1.GetStateDiff(sv2)
 68		if diff1 == nil {
 69			t.Fatal("failed to get state diff 1")
 70		}
 71		return nil
 72	})
 73	if err != nil {
 74		t.Fatalf("failed d1 second transaction: %v", err)
 75	}
 76
 77	// Fourth transaction on d2: calculate diff using sv1
 78	err = d2.WithWriteTransaction(func(txn2 *ygo.Transaction) error {
 79		diff2 = txn2.GetStateDiff(sv1)
 80		if diff2 == nil {
 81			t.Fatal("failed to get state diff 2")
 82		}
 83		return nil
 84	})
 85	if err != nil {
 86		t.Fatalf("failed d2 second transaction: %v", err)
 87	}
 88
 89	// Fifth transaction on d1: apply d2's diff
 90	err = d1.WithWriteTransaction(func(txn1 *ygo.Transaction) error {
 91		if err := txn1.ApplyUpdate(diff2); err != nil {
 92			t.Errorf("failed to apply update to d1: %v", err)
 93		}
 94		return nil
 95	})
 96	if err != nil {
 97		t.Fatalf("failed d1 third transaction: %v", err)
 98	}
 99
100	// Sixth transaction on d2: apply d1's diff
101	err = d2.WithWriteTransaction(func(txn2 *ygo.Transaction) error {
102		if err := txn2.ApplyUpdate(diff1); err != nil {
103			t.Errorf("failed to apply update to d2: %v", err)
104		}
105		return nil
106	})
107	if err != nil {
108		t.Fatalf("failed d2 third transaction: %v", err)
109	}
110
111	// Both should converge to same content
112	var str1, str2 string
113
114	err = d1.WithReadTransaction(func(txn1 *ygo.Transaction) error {
115		var err error
116		str1, err = txt1.String(txn1)
117		if err != nil {
118			t.Fatalf("failed to get string 1: %v", err)
119		}
120		return nil
121	})
122	if err != nil {
123		t.Fatalf("failed d1 read transaction: %v", err)
124	}
125
126	err = d2.WithReadTransaction(func(txn2 *ygo.Transaction) error {
127		var err error
128		str2, err = txt2.String(txn2)
129		if err != nil {
130			t.Fatalf("failed to get string 2: %v", err)
131		}
132		return nil
133	})
134	if err != nil {
135		t.Fatalf("failed d2 read transaction: %v", err)
136	}
137
138	if str1 != str2 {
139		t.Errorf("documents diverged: '%s' vs '%s'", str1, str2)
140	}
141
142	// Should be "hello world" (order depends on CRDT semantics)
143	expected := "hello world"
144	if str1 != expected && str1 != "worldhello " {
145		t.Errorf("unexpected content: '%s'", str1)
146	}
147}
148
149func TestFullStateSnapshot(t *testing.T) {
150	d1, err := ygo.NewDoc()
151	if err != nil {
152		t.Fatalf("failed to create doc1: %v", err)
153	}
154	defer d1.Destroy()
155
156	txt1, err := d1.GetText("test")
157	if err != nil {
158		t.Fatalf("failed to get text1: %v", err)
159	}
160	defer txt1.Destroy()
161
162	// Variable to capture snapshot between transactions
163	var snapshot *ygo.Update
164
165	// Create content and get full state snapshot (nil remote state = full state)
166	err = d1.WithWriteTransaction(func(txn *ygo.Transaction) error {
167		txt1.Insert(txn, 0, "hello world")
168		snapshot = txn.GetStateDiff(nil)
169		if snapshot == nil {
170			t.Fatal("failed to get state diff")
171		}
172		return nil
173	})
174	if err != nil {
175		t.Fatalf("failed d1 transaction: %v", err)
176	}
177
178	// Create new document and apply snapshot
179	d2, err := ygo.NewDoc()
180	if err != nil {
181		t.Fatalf("failed to create doc2: %v", err)
182	}
183	defer d2.Destroy()
184
185	txt2, err := d2.GetText("test")
186	if err != nil {
187		t.Fatalf("failed to get text2: %v", err)
188	}
189	defer txt2.Destroy()
190
191	err = d2.WithWriteTransaction(func(txn *ygo.Transaction) error {
192		if err := txn.ApplyUpdate(snapshot); err != nil {
193			t.Errorf("failed to apply snapshot: %v", err)
194		}
195		return nil
196	})
197	if err != nil {
198		t.Fatalf("failed d2 transaction: %v", err)
199	}
200
201	// Verify content
202	var str string
203	err = d2.WithReadTransaction(func(txn *ygo.Transaction) error {
204		var err error
205		str, err = txt2.String(txn)
206		if err != nil {
207			t.Fatalf("failed to get string: %v", err)
208		}
209		return nil
210	})
211	if err != nil {
212		t.Fatalf("failed d2 read transaction: %v", err)
213	}
214
215	if str != "hello world" {
216		t.Errorf("expected 'hello world', got '%s'", str)
217	}
218}