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}