document_test.go

  1package ygo_test
  2
  3import (
  4	"encoding/json"
  5	"git.kilimanjaro.io/ygo"
  6	"testing"
  7)
  8
  9func TestNewDoc(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	if doc.ClientID() == 0 {
 17		t.Error("expected non-zero client ID")
 18	}
 19
 20	guid := doc.GUID()
 21	if guid == "" {
 22		t.Error("expected non-empty GUID")
 23	}
 24}
 25
 26func TestNewDocWithOptions(t *testing.T) {
 27	opts := ygo.DocOptions{
 28		ClientID: 123,
 29		GUID:     "test-guid",
 30		Encoding: ygo.OffsetUTF16,
 31		SkipGC:   true,
 32	}
 33	doc, err := ygo.NewDocWithOptions(opts)
 34	if err != nil {
 35		t.Fatalf("failed to create doc with options: %v", err)
 36	}
 37	defer doc.Destroy()
 38
 39	if doc.ClientID() != 123 {
 40		t.Errorf("expected client ID 123, got %d", doc.ClientID())
 41	}
 42
 43	if doc.GUID() != "test-guid" {
 44		t.Errorf("expected GUID 'test-guid', got %s", doc.GUID())
 45	}
 46}
 47
 48func TestDocClone(t *testing.T) {
 49	doc, err := ygo.NewDoc()
 50	if err != nil {
 51		t.Fatalf("failed to create doc: %v", err)
 52	}
 53	defer doc.Destroy()
 54
 55	clone, err := doc.Clone()
 56	if err != nil {
 57		t.Fatalf("failed to clone doc: %v", err)
 58	}
 59	defer clone.Destroy()
 60
 61	if clone.ClientID() != doc.ClientID() {
 62		t.Error("clone should have same client ID")
 63	}
 64}
 65
 66func TestDocDeepClone(t *testing.T) {
 67	doc, err := ygo.NewDoc()
 68	if err != nil {
 69		t.Fatalf("failed to create doc: %v", err)
 70	}
 71	defer doc.Destroy()
 72
 73	txt, err := doc.GetText("content")
 74	if err != nil {
 75		t.Fatalf("failed to get text: %v", err)
 76	}
 77	defer txt.Destroy()
 78
 79	err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
 80		return txt.Push(txn, "Hello")
 81	})
 82	if err != nil {
 83		t.Fatalf("transaction failed: %v", err)
 84	}
 85
 86	clone, err := doc.DeepClone()
 87	if err != nil {
 88		t.Fatalf("DeepClone failed: %v", err)
 89	}
 90	defer clone.Destroy()
 91
 92	// Verify clone has same content - get text BEFORE transaction
 93	txt2, err := clone.GetText("content")
 94	if err != nil {
 95		t.Fatalf("failed to get text from clone: %v", err)
 96	}
 97	defer txt2.Destroy()
 98
 99	var cloneContent string
100	err = clone.WithReadTransaction(func(txn *ygo.Transaction) error {
101		cloneContent, err = txt2.String(txn)
102		return err
103	})
104	if err != nil {
105		t.Fatalf("failed to read clone: %v", err)
106	}
107	if cloneContent != "Hello" {
108		t.Errorf("expected 'Hello', got '%s'", cloneContent)
109	}
110
111	// Verify independence - change original doesn't affect clone
112	err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
113		return txt.Push(txn, " World")
114	})
115	if err != nil {
116		t.Fatalf("failed to modify original: %v", err)
117	}
118
119	// Clone should still have old content
120	if cloneContent != "Hello" {
121		t.Error("clone was affected by change to original - not independent")
122	}
123}
124
125func TestDocDestroy(t *testing.T) {
126	doc, err := ygo.NewDoc()
127	if err != nil {
128		t.Fatalf("failed to create doc: %v", err)
129	}
130	doc.Destroy()
131
132	// Second destroy should be safe
133	doc.Destroy()
134}
135
136func TestMarshalUnmarshalRoundTrip(t *testing.T) {
137	// Create document with content
138	doc1, err := ygo.NewDoc()
139	if err != nil {
140		t.Fatalf("failed to create doc1: %v", err)
141	}
142	defer doc1.Destroy()
143
144	txt1, err := doc1.GetText("content")
145	if err != nil {
146		t.Fatalf("failed to get text: %v", err)
147	}
148	defer txt1.Destroy()
149
150	// Add some content
151	err = doc1.WithWriteTransaction(func(txn *ygo.Transaction) error {
152		txt1.Insert(txn, 0, "Hello World!")
153		return nil
154	})
155	if err != nil {
156		t.Fatalf("failed to insert text: %v", err)
157	}
158
159	// Marshal the document
160	data, err := doc1.MarshalBinary()
161	if err != nil {
162		t.Fatalf("failed to marshal: %v", err)
163	}
164	if len(data) == 0 {
165		t.Fatal("marshal returned empty data")
166	}
167
168	// Create new document and unmarshal
169	doc2, err := ygo.NewDoc()
170	if err != nil {
171		t.Fatalf("failed to create doc2: %v", err)
172	}
173	defer doc2.Destroy()
174
175	// IMPORTANT: Initialize shared types before unmarshaling
176	txt2, err := doc2.GetText("content")
177	if err != nil {
178		t.Fatalf("failed to get text2: %v", err)
179	}
180	defer txt2.Destroy()
181
182	// Unmarshal the data
183	if err := doc2.UnmarshalBinary(data); err != nil {
184		t.Fatalf("failed to unmarshal: %v", err)
185	}
186
187	// Verify content matches
188	var content string
189	err = doc2.WithReadTransaction(func(txn *ygo.Transaction) error {
190		var err error
191		content, err = txt2.String(txn)
192		return err
193	})
194	if err != nil {
195		t.Fatalf("failed to read content: %v", err)
196	}
197
198	if content != "Hello World!" {
199		t.Errorf("expected 'Hello World!', got '%s'", content)
200	}
201}
202
203func TestMarshalNilDocument(t *testing.T) {
204	var doc *ygo.Doc
205	data, err := doc.MarshalBinary()
206	if err == nil {
207		t.Error("expected error when marshaling nil document")
208	}
209	if data != nil {
210		t.Error("expected nil data when marshaling nil document")
211	}
212}
213
214func TestUnmarshalNilDocument(t *testing.T) {
215	var doc *ygo.Doc
216	err := doc.UnmarshalBinary([]byte{1, 2, 3})
217	if err == nil {
218		t.Error("expected error when unmarshaling to nil document")
219	}
220}
221
222func TestUnmarshalEmptyData(t *testing.T) {
223	doc, err := ygo.NewDoc()
224	if err != nil {
225		t.Fatalf("failed to create doc: %v", err)
226	}
227	defer doc.Destroy()
228
229	if err := doc.UnmarshalBinary([]byte{}); err != nil {
230		t.Fatalf("failed to unmarshal empty data: %v", err)
231	}
232	if err := doc.UnmarshalBinary(nil); err != nil {
233		t.Fatalf("failed to unmarshal nil data: %v", err)
234	}
235}
236
237func TestMarshalEmptyDocument(t *testing.T) {
238	doc, err := ygo.NewDoc()
239	if err != nil {
240		t.Fatalf("failed to create doc: %v", err)
241	}
242	defer doc.Destroy()
243
244	data, err := doc.MarshalBinary()
245	if err != nil {
246		t.Fatalf("failed to marshal empty doc: %v", err)
247	}
248	if len(data) == 0 {
249		t.Error("marshal of empty document should return some data (metadata)")
250	}
251}
252
253func TestDocumentMarshalJSONEmpty(t *testing.T) {
254	doc, err := ygo.NewDoc()
255	if err != nil {
256		t.Fatalf("failed to create doc: %v", err)
257	}
258	defer doc.Destroy()
259
260	jsonBytes, err := doc.MarshalJSON()
261	if err != nil {
262		t.Fatalf("marshal failed: %v", err)
263	}
264
265	if string(jsonBytes) != "{}" {
266		t.Errorf("expected {}, got %s", string(jsonBytes))
267	}
268}
269
270func TestDocumentMarshalJSONNonexistent(t *testing.T) {
271	doc, err := ygo.NewDoc()
272	if err != nil {
273		t.Fatalf("failed to create doc: %v", err)
274	}
275	defer doc.Destroy()
276
277	jsonBytes, err := doc.MarshalJSON(ygo.WithMap("nonexistent"))
278	if err != nil {
279		t.Fatalf("marshal should succeed for nonexistent roots (ygo creates them): %v", err)
280	}
281
282	var result map[string]interface{}
283	if err := json.Unmarshal(jsonBytes, &result); err != nil {
284		t.Fatalf("failed to unmarshal: %v", err)
285	}
286
287	if _, ok := result["nonexistent"]; !ok {
288		t.Error("expected nonexistent map to be created and included")
289	}
290}
291
292func TestDocumentMarshalJSONWithOptions(t *testing.T) {
293	doc, err := ygo.NewDoc()
294	if err != nil {
295		t.Fatalf("failed to create doc: %v", err)
296	}
297	defer doc.Destroy()
298
299	m, err := doc.GetMap("users")
300	if err != nil {
301		t.Fatalf("failed to get map: %v", err)
302	}
303	defer m.Destroy()
304
305	arr, err := doc.GetArray("items")
306	if err != nil {
307		t.Fatalf("failed to get array: %v", err)
308	}
309	defer arr.Destroy()
310
311	err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
312		m.Insert(txn, "name", ygo.String("Alice"))
313		m.Insert(txn, "age", ygo.Int(30))
314		arr.Push(txn, ygo.String("item1"))
315		arr.Push(txn, ygo.String("item2"))
316		return nil
317	})
318	if err != nil {
319		t.Fatalf("transaction failed: %v", err)
320	}
321
322	jsonBytes, err := doc.MarshalJSON(
323		ygo.WithMap("users"),
324		ygo.WithArray("items"),
325	)
326	if err != nil {
327		t.Fatalf("marshal failed: %v", err)
328	}
329
330	var result map[string]interface{}
331	if err := json.Unmarshal(jsonBytes, &result); err != nil {
332		t.Fatalf("failed to unmarshal: %v", err)
333	}
334
335	users, ok := result["users"].(map[string]interface{})
336	if !ok {
337		t.Fatal("expected users to be a map")
338	}
339	if users["name"] != "Alice" {
340		t.Errorf("expected users.name=Alice, got %v", users["name"])
341	}
342	if users["age"] != float64(30) {
343		t.Errorf("expected users.age=30, got %v", users["age"])
344	}
345
346	items, ok := result["items"].([]interface{})
347	if !ok {
348		t.Fatal("expected items to be an array")
349	}
350	if len(items) != 2 {
351		t.Errorf("expected 2 items, got %d", len(items))
352	}
353	if items[0] != "item1" {
354		t.Errorf("expected items[0]=item1, got %v", items[0])
355	}
356	if items[1] != "item2" {
357		t.Errorf("expected items[1]=item2, got %v", items[1])
358	}
359}
360
361func TestDocumentMarshalJSONWithXmlFragment(t *testing.T) {
362	doc, err := ygo.NewDoc()
363	if err != nil {
364		t.Fatalf("failed to create doc: %v", err)
365	}
366	defer doc.Destroy()
367
368	f, err := doc.GetXmlFragment("content")
369	if err != nil {
370		t.Fatalf("failed to get xml fragment: %v", err)
371	}
372	defer f.Destroy()
373
374	err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
375		elem, err := f.InsertElement(txn, 0, "note")
376		if err != nil {
377			return err
378		}
379		defer elem.Destroy()
380
381		txt, err := elem.InsertText(txn, 0)
382		if err != nil {
383			return err
384		}
385		defer txt.Destroy()
386
387		txt.Insert(txn, 0, "Hello World")
388		return nil
389	})
390	if err != nil {
391		t.Fatalf("transaction failed: %v", err)
392	}
393
394	jsonBytes, err := doc.MarshalJSON(ygo.WithXmlFragment("content"))
395	if err != nil {
396		t.Fatalf("marshal failed: %v", err)
397	}
398
399	var result map[string]interface{}
400	if err := json.Unmarshal(jsonBytes, &result); err != nil {
401		t.Fatalf("failed to unmarshal: %v", err)
402	}
403
404	content, ok := result["content"].([]interface{})
405	if !ok {
406		t.Fatal("expected content to be an array")
407	}
408	if len(content) == 0 {
409		t.Fatal("expected at least one element in content array")
410	}
411}
412
413func TestDocumentMarshalJSONWithText(t *testing.T) {
414	doc, err := ygo.NewDoc()
415	if err != nil {
416		t.Fatalf("failed to create doc: %v", err)
417	}
418	defer doc.Destroy()
419
420	txt, err := doc.GetText("schema")
421	if err != nil {
422		t.Fatalf("failed to get text: %v", err)
423	}
424	defer txt.Destroy()
425
426	err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
427		return txt.Push(txn, "some-schema-value")
428	})
429	if err != nil {
430		t.Fatalf("transaction failed: %v", err)
431	}
432
433	jsonBytes, err := doc.MarshalJSON(ygo.WithText("schema"))
434	if err != nil {
435		t.Fatalf("marshal failed: %v", err)
436	}
437
438	var result map[string]interface{}
439	if err := json.Unmarshal(jsonBytes, &result); err != nil {
440		t.Fatalf("failed to unmarshal: %v", err)
441	}
442
443	if result["schema"] != "some-schema-value" {
444		t.Errorf("expected schema=some-schema-value, got %v", result["schema"])
445	}
446}