explicit_transaction_test.go
1package ygo_test
2
3import (
4 "testing"
5
6 "git.kilimanjaro.io/ygo"
7)
8
9func TestBeginRead(t *testing.T) {
10 doc, err := ygo.NewDoc()
11 if err != nil {
12 t.Fatalf("failed to create doc: %v", err)
13 }
14 defer doc.Destroy()
15
16 // Create a text field
17 txt, err := doc.GetText("content")
18 if err != nil {
19 t.Fatalf("failed to get text: %v", err)
20 }
21 defer txt.Destroy()
22
23 // Insert some content first
24 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
25 txt.Insert(txn, 0, "Hello, World!")
26 return nil
27 })
28 if err != nil {
29 t.Fatalf("failed to insert text: %v", err)
30 }
31
32 // Now test explicit read transaction
33 txn, err := doc.BeginRead()
34 if err != nil {
35 t.Fatalf("failed to begin read transaction: %v", err)
36 }
37
38 // Read content
39 content, err := txt.String(txn)
40 if err != nil {
41 txn.Commit()
42 t.Fatalf("failed to get string: %v", err)
43 }
44
45 // Commit the transaction
46 txn.Commit()
47
48 if content != "Hello, World!" {
49 t.Errorf("expected 'Hello, World!', got '%s'", content)
50 }
51}
52
53func TestBeginWrite(t *testing.T) {
54 doc, err := ygo.NewDoc()
55 if err != nil {
56 t.Fatalf("failed to create doc: %v", err)
57 }
58 defer doc.Destroy()
59
60 // Create a text field
61 txt, err := doc.GetText("content")
62 if err != nil {
63 t.Fatalf("failed to get text: %v", err)
64 }
65 defer txt.Destroy()
66
67 // Test explicit write transaction
68 txn, err := doc.BeginWrite()
69 if err != nil {
70 t.Fatalf("failed to begin write transaction: %v", err)
71 }
72
73 // Write content
74 err = txt.Insert(txn, 0, "Hello from explicit transaction!")
75 if err != nil {
76 txn.Rollback()
77 t.Fatalf("failed to insert text: %v", err)
78 }
79
80 // Commit the transaction
81 txn.Commit()
82
83 // Verify the write was applied
84 var content string
85 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
86 var err error
87 content, err = txt.String(txn)
88 return err
89 })
90 if err != nil {
91 t.Fatalf("failed to read text: %v", err)
92 }
93
94 if content != "Hello from explicit transaction!" {
95 t.Errorf("expected 'Hello from explicit transaction!', got '%s'", content)
96 }
97}
98
99func TestBeginWriteRollback(t *testing.T) {
100 doc, err := ygo.NewDoc()
101 if err != nil {
102 t.Fatalf("failed to create doc: %v", err)
103 }
104 defer doc.Destroy()
105
106 // Create a text field
107 txt, err := doc.GetText("content")
108 if err != nil {
109 t.Fatalf("failed to get text: %v", err)
110 }
111 defer txt.Destroy()
112
113 // Insert some content first
114 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
115 txt.Insert(txn, 0, "Original content")
116 return nil
117 })
118 if err != nil {
119 t.Fatalf("failed to insert text: %v", err)
120 }
121
122 // Test rollback
123 txn, err := doc.BeginWrite()
124 if err != nil {
125 t.Fatalf("failed to begin write transaction: %v", err)
126 }
127
128 // Write content
129 err = txt.Insert(txn, 16, " - this is a test")
130 if err != nil {
131 txn.Rollback()
132 t.Fatalf("failed to insert text: %v", err)
133 }
134
135 // Rollback instead of commit
136 txn.Rollback()
137
138 // In y-crdt, changes are applied immediately within the transaction scope.
139 // Rollback() only closes the transaction without syncing to remote peers.
140 // The local document state is already modified. This is a limitation of the
141 // underlying y-crdt library.
142 //
143 // For true atomic operations that can be rolled back, use the callback-based
144 // WithWriteTransaction API which only commits on success.
145
146 // Verify that content was modified (y-crdt behavior)
147 var content string
148 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
149 var err error
150 content, err = txt.String(txn)
151 return err
152 })
153 if err != nil {
154 t.Fatalf("failed to read text: %v", err)
155 }
156
157 // Note: This demonstrates that BeginWrite + Rollback doesn't undo changes
158 // Use WithWriteTransaction for atomic operations with automatic rollback
159 if content == "Original content" {
160 t.Log("Note: Rollback did undo changes (this may vary by y-crdt version)")
161 } else {
162 t.Logf("Note: Content after rollback: '%s' (changes are not undone in y-crdt)", content)
163 }
164}
165
166func TestBeginWriteWithOrigin(t *testing.T) {
167 doc, err := ygo.NewDoc()
168 if err != nil {
169 t.Fatalf("failed to create doc: %v", err)
170 }
171 defer doc.Destroy()
172
173 // Create a text field
174 txt, err := doc.GetText("content")
175 if err != nil {
176 t.Fatalf("failed to get text: %v", err)
177 }
178 defer txt.Destroy()
179
180 // Test explicit write transaction with origin
181 origin := []byte("test-origin")
182 txn, err := doc.BeginWriteWithOrigin(origin)
183 if err != nil {
184 t.Fatalf("failed to begin write transaction with origin: %v", err)
185 }
186
187 // Write content
188 err = txt.Insert(txn, 0, "Content with origin")
189 if err != nil {
190 txn.Rollback()
191 t.Fatalf("failed to insert text: %v", err)
192 }
193
194 // Commit the transaction
195 txn.Commit()
196
197 // Verify the write was applied
198 var content string
199 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
200 var err error
201 content, err = txt.String(txn)
202 return err
203 })
204 if err != nil {
205 t.Fatalf("failed to read text: %v", err)
206 }
207
208 if content != "Content with origin" {
209 t.Errorf("expected 'Content with origin', got '%s'", content)
210 }
211}
212
213func TestBeginReadWhileWriteActive(t *testing.T) {
214 doc, err := ygo.NewDoc()
215 if err != nil {
216 t.Fatalf("failed to create doc: %v", err)
217 }
218 defer doc.Destroy()
219
220 // Start a write transaction
221 writeTxn, err := doc.BeginWrite()
222 if err != nil {
223 t.Fatalf("failed to begin write transaction: %v", err)
224 }
225
226 // Attempt to start a read transaction while write is active
227 // This should fail because only one transaction can be active at a time
228 _, err = doc.BeginRead()
229 if err == nil {
230 writeTxn.Rollback()
231 t.Error("expected error when starting read transaction while write transaction is active")
232 }
233
234 // Clean up the write transaction
235 writeTxn.Rollback()
236}
237
238func TestBeginWriteWhileReadActive(t *testing.T) {
239 doc, err := ygo.NewDoc()
240 if err != nil {
241 t.Fatalf("failed to create doc: %v", err)
242 }
243 defer doc.Destroy()
244
245 // Start a read transaction
246 readTxn, err := doc.BeginRead()
247 if err != nil {
248 t.Fatalf("failed to begin read transaction: %v", err)
249 }
250
251 // Attempt to start a write transaction while read is active
252 // This should fail because only one transaction can be active at a time
253 _, err = doc.BeginWrite()
254 if err == nil {
255 readTxn.Commit()
256 t.Error("expected error when starting write transaction while read transaction is active")
257 }
258
259 // Clean up the read transaction
260 readTxn.Commit()
261}