Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

collection_test.gno

20.95 Kb · 987 lines
  1package collection
  2
  3import (
  4	"errors"
  5	"strconv"
  6	"strings"
  7	"testing"
  8
  9	"gno.land/p/nt/seqid/v0"
 10	"gno.land/p/nt/ufmt/v0"
 11)
 12
 13type Person struct {
 14	Name     string
 15	Age      int
 16	Email    string
 17	Username string
 18	Tags     []string
 19}
 20
 21func (p Person) String() string {
 22	return ufmt.Sprintf("name=%s age=%d email=%s username=%s tags=%s",
 23		p.Name, p.Age, p.Email, p.Username, strings.Join(p.Tags, ","))
 24}
 25
 26// TestOperation represents a single operation in a test sequence
 27type TestOperation struct {
 28	op      string // "set" or "update"
 29	person  *Person
 30	id      uint64 // for updates
 31	wantID  uint64
 32	wantErr bool
 33}
 34
 35// TestCase represents a complete test case with setup and operations
 36type TestCase struct {
 37	name       string
 38	setupIndex func(*Collection)
 39	operations []TestOperation
 40}
 41
 42func TestBasicOperations(t *testing.T) {
 43	c := New()
 44
 45	// Add indexes
 46	c.AddIndex("name", func(v any) string {
 47		return v.(*Person).Name
 48	}, UniqueIndex)
 49	c.AddIndex("age", func(v any) string {
 50		return strconv.Itoa(v.(*Person).Age)
 51	}, DefaultIndex)
 52
 53	// Test basic Set and Get
 54	p1 := &Person{Name: "Alice", Age: 30, Email: "[email protected]"}
 55	id1 := c.Set(p1)
 56	if id1 == 0 {
 57		t.Error("Failed to set first object")
 58	}
 59
 60	// Get by ID
 61	iter := c.Get(IDIndex, seqid.ID(id1).String())
 62	if !iter.Next() {
 63		t.Error("Failed to get object by ID")
 64	}
 65	entry := iter.Value()
 66	if entry.Obj.(*Person).Name != "Alice" {
 67		t.Error("Got wrong object")
 68	}
 69}
 70
 71func TestUniqueConstraints(t *testing.T) {
 72	tests := []struct {
 73		name   string
 74		setup  func(*Collection) uint64
 75		wantID bool
 76	}{
 77		{
 78			name: "First person",
 79			setup: func(c *Collection) uint64 {
 80				return c.Set(&Person{Name: "Alice"})
 81			},
 82			wantID: true,
 83		},
 84		{
 85			name: "Duplicate name",
 86			setup: func(c *Collection) uint64 {
 87				c.Set(&Person{Name: "Alice"})
 88				return c.Set(&Person{Name: "Alice"})
 89			},
 90			wantID: false,
 91		},
 92		{
 93			name: "Same age (non-unique index)",
 94			setup: func(c *Collection) uint64 {
 95				c.AddIndex("age", func(v any) string {
 96					return strconv.Itoa(v.(*Person).Age)
 97				}, DefaultIndex)
 98				c.Set(&Person{Name: "Alice", Age: 30})
 99				return c.Set(&Person{Name: "Bob", Age: 30})
100			},
101			wantID: true,
102		},
103	}
104
105	for _, tt := range tests {
106		t.Run(tt.name, func(t *testing.T) {
107			c := New()
108			c.AddIndex("name", func(v any) string {
109				return v.(*Person).Name
110			}, UniqueIndex)
111
112			id := tt.setup(c)
113			if (id != 0) != tt.wantID {
114				t.Errorf("Set() got id = %v, want non-zero: %v", id, tt.wantID)
115			}
116		})
117	}
118}
119
120func TestUpdates(t *testing.T) {
121	c := New()
122	c.AddIndex("name", func(v any) string {
123		return v.(*Person).Name
124	}, UniqueIndex)
125	c.AddIndex("username", func(v any) string {
126		return strings.ToLower(v.(*Person).Username)
127	}, UniqueIndex|CaseInsensitiveIndex)
128
129	// Initial setup
130	p1 := &Person{Name: "Alice", Username: "alice123"}
131	p2 := &Person{Name: "Bob", Username: "bob456"}
132
133	id1 := c.Set(p1)
134	c.Set(p2)
135
136	tests := []struct {
137		name      string
138		id        uint64
139		newPerson *Person
140		wantRet   bool
141	}{
142		{
143			name:      "Update to non-conflicting values",
144			id:        id1,
145			newPerson: &Person{Name: "Alice2", Username: "alice1234"},
146			wantRet:   true,
147		},
148		{
149			name:      "Update to conflicting username",
150			id:        id1,
151			newPerson: &Person{Name: "Alice2", Username: "bob456"},
152			wantRet:   false,
153		},
154		{
155			name:      "Update non-existent ID",
156			id:        99999,
157			newPerson: &Person{Name: "Test", Username: "test"},
158			wantRet:   false,
159		},
160	}
161
162	for _, tt := range tests {
163		t.Run(tt.name, func(t *testing.T) {
164			gotID := c.Update(tt.id, tt.newPerson)
165			if gotID != tt.wantRet {
166				t.Errorf("Update() got = %v, want %v", gotID, tt.wantRet)
167			}
168		})
169	}
170}
171
172func TestDelete(t *testing.T) {
173	c := New()
174	c.AddIndex("name", func(v any) string {
175		return v.(*Person).Name
176	}, UniqueIndex)
177
178	p1 := &Person{Name: "Alice"}
179	id1 := c.Set(p1)
180
181	tests := []struct {
182		name    string
183		id      uint64
184		wantRet bool
185	}{
186		{
187			name:    "Delete existing object",
188			id:      id1,
189			wantRet: true,
190		},
191		{
192			name:    "Delete non-existent object",
193			id:      99999,
194			wantRet: false,
195		},
196		{
197			name:    "Delete already deleted object",
198			id:      id1,
199			wantRet: false,
200		},
201	}
202
203	for _, tt := range tests {
204		t.Run(tt.name, func(t *testing.T) {
205			gotID := c.Delete(tt.id)
206			if gotID != tt.wantRet {
207				t.Errorf("Delete() got = %v, want %v", gotID, tt.wantRet)
208			}
209		})
210	}
211}
212
213func TestEdgeCases(t *testing.T) {
214	c := New()
215	c.AddIndex("name", func(v any) string {
216		return v.(*Person).Name
217	}, UniqueIndex)
218
219	tests := []struct {
220		name      string
221		operation func() bool
222		wantPanic bool
223	}{
224		{
225			name: "Set nil object",
226			operation: func() bool {
227				return c.Set(nil) != 0
228			},
229			wantPanic: false,
230		},
231		{
232			name: "Set wrong type",
233			operation: func() bool {
234				return c.Set("not a person") != 0
235			},
236			wantPanic: true,
237		},
238		{
239			name: "Update with nil",
240			operation: func() bool {
241				id := c.Set(&Person{Name: "Test"})
242				return c.Update(id, nil)
243			},
244			wantPanic: false,
245		},
246		{
247			name: "Get with invalid index name",
248			operation: func() bool {
249				iter := c.Get("invalid_index", "key")
250				if iter.Empty() {
251					return false
252				}
253				entry := iter.Value()
254				if entry == nil {
255					return false
256				}
257				_, err := seqid.FromString(entry.ID)
258				if err != nil {
259					return false
260				}
261				return true
262			},
263			wantPanic: false,
264		},
265	}
266
267	for _, tt := range tests {
268		t.Run(tt.name, func(t *testing.T) {
269			var got bool
270			panicked := false
271
272			func() {
273				defer func() {
274					if r := recover(); r != nil {
275						panicked = true
276					}
277				}()
278				got = tt.operation()
279			}()
280
281			if panicked != tt.wantPanic {
282				t.Errorf("Operation panicked = %v, want panic = %v", panicked, tt.wantPanic)
283			}
284			if !panicked && got != false {
285				t.Errorf("Operation returned %v, want 0", got)
286			}
287		})
288	}
289}
290
291func TestIndexTypes(t *testing.T) {
292	c := New()
293
294	// Test different types of indexes
295	c.AddIndex("composite", func(v any) string {
296		p := v.(*Person)
297		return p.Name + ":" + strconv.Itoa(p.Age)
298	}, UniqueIndex)
299
300	c.AddIndex("case_insensitive", func(v any) string {
301		return strings.ToLower(v.(*Person).Username)
302	}, UniqueIndex|CaseInsensitiveIndex)
303
304	// Test composite index
305	p1 := &Person{Name: "Alice", Age: 30, Username: "Alice123"}
306	id1 := c.Set(p1)
307	if id1 == 0 {
308		t.Error("Failed to set object with composite index")
309	}
310
311	// Test case-insensitive index
312	p2 := &Person{Name: "Bob", Age: 25, Username: "alice123"}
313	id2 := c.Set(p2)
314	if id2 != 0 {
315		t.Error("Case-insensitive index failed to prevent duplicate")
316	}
317}
318
319func TestIndexOptions(t *testing.T) {
320	tests := []struct {
321		name    string
322		setup   func(*Collection) uint64
323		wantID  bool
324		wantErr bool
325	}{
326		{
327			name: "Unique case-sensitive index",
328			setup: func(c *Collection) uint64 {
329				c.AddIndex("username", func(v any) string {
330					return v.(*Person).Username
331				}, UniqueIndex)
332
333				c.Set(&Person{Username: "Alice"})
334				return c.Set(&Person{Username: "Alice"}) // Should fail
335			},
336			wantID: false,
337		},
338		{
339			name: "Unique case-insensitive index",
340			setup: func(c *Collection) uint64 {
341				c.AddIndex("email", func(v any) string {
342					return v.(*Person).Email
343				}, UniqueIndex|CaseInsensitiveIndex)
344
345				c.Set(&Person{Email: "[email protected]"})
346				return c.Set(&Person{Email: "[email protected]"}) // Should fail
347			},
348			wantID: false,
349		},
350		{
351			name: "Default index",
352			setup: func(c *Collection) uint64 {
353				c.AddIndex("age", func(v any) string {
354					return strconv.Itoa(v.(*Person).Age)
355				}, DefaultIndex)
356
357				// First person with age 30
358				id1 := c.Set(&Person{Age: 30})
359				if id1 == 0 {
360					t.Error("Failed to set first person")
361				}
362
363				// Second person with same age should succeed
364				return c.Set(&Person{Age: 30})
365			},
366			wantID: true,
367		},
368		{
369			name: "Multiple options",
370			setup: func(c *Collection) uint64 {
371				c.AddIndex("name", func(v any) string {
372					return v.(*Person).Name
373				}, UniqueIndex|CaseInsensitiveIndex|SparseIndex)
374
375				c.Set(&Person{Name: "Alice"})
376				return c.Set(&Person{Name: "ALICE"}) // Should fail
377			},
378			wantID: false,
379		},
380	}
381
382	for _, tt := range tests {
383		t.Run(tt.name, func(t *testing.T) {
384			c := New() // Create new collection for each test
385			id := tt.setup(c)
386			if (id != 0) != tt.wantID {
387				t.Errorf("got id = %v, want non-zero: %v", id, tt.wantID)
388			}
389		})
390	}
391}
392
393func TestConcurrentOperations(t *testing.T) {
394	c := New()
395	c.AddIndex("name", func(v any) string {
396		return v.(*Person).Name
397	}, UniqueIndex)
398
399	p1 := &Person{Name: "Alice"}
400	id1 := c.Set(p1)
401	iter := c.Get("_id", seqid.ID(id1).String())
402	success := c.Update(id1, &Person{Name: "Alice2"})
403
404	if iter.Empty() || !success {
405		t.Error("Concurrent operations failed")
406	}
407}
408
409func TestSparseIndexBehavior(t *testing.T) {
410	c := New()
411	c.AddIndex("optional_field", func(v any) string {
412		return v.(*Person).Username
413	}, SparseIndex)
414
415	tests := []struct {
416		name   string
417		person *Person
418		wantID bool
419	}{
420		{
421			name:   "Empty optional field",
422			person: &Person{Name: "Alice", Email: "[email protected]"},
423			wantID: true,
424		},
425		{
426			name:   "Populated optional field",
427			person: &Person{Name: "Bob", Email: "[email protected]", Username: "bobby"},
428			wantID: true,
429		},
430		{
431			name:   "Multiple empty fields",
432			person: &Person{Name: "Charlie"},
433			wantID: true,
434		},
435	}
436
437	for _, tt := range tests {
438		t.Run(tt.name, func(t *testing.T) {
439			id := c.Set(tt.person)
440			if (id != 0) != tt.wantID {
441				t.Errorf("Set() got id = %v, want non-zero: %v", id, tt.wantID)
442			}
443		})
444	}
445}
446
447func TestIndexKeyGeneration(t *testing.T) {
448	c := New()
449	c.AddIndex("composite", func(v any) string {
450		p := v.(*Person)
451		return p.Name + ":" + strconv.Itoa(p.Age)
452	}, UniqueIndex)
453
454	tests := []struct {
455		name   string
456		person *Person
457		wantID bool
458	}{
459		{
460			name:   "Valid composite key",
461			person: &Person{Name: "Alice", Age: 30},
462			wantID: true,
463		},
464		{
465			name:   "Duplicate composite key",
466			person: &Person{Name: "Alice", Age: 30},
467			wantID: false,
468		},
469		{
470			name:   "Different composite key",
471			person: &Person{Name: "Alice", Age: 31},
472			wantID: true,
473		},
474	}
475
476	for _, tt := range tests {
477		t.Run(tt.name, func(t *testing.T) {
478			id := c.Set(tt.person)
479			if (id != 0) != tt.wantID {
480				t.Errorf("Set() got id = %v, want non-zero: %v", id, tt.wantID)
481			}
482		})
483	}
484}
485
486func TestGetIndex(t *testing.T) {
487	c := New()
488	c.AddIndex("name", func(v any) string {
489		return v.(*Person).Name
490	}, UniqueIndex)
491
492	tests := []struct {
493		name      string
494		indexName string
495		wantNil   bool
496	}{
497		{
498			name:      "Get existing index",
499			indexName: "name",
500			wantNil:   false,
501		},
502		{
503			name:      "Get _id index",
504			indexName: IDIndex,
505			wantNil:   false,
506		},
507		{
508			name:      "Get non-existent index",
509			indexName: "invalid",
510			wantNil:   true,
511		},
512	}
513
514	for _, tt := range tests {
515		t.Run(tt.name, func(t *testing.T) {
516			tree := c.GetIndex(tt.indexName)
517			if (tree == nil) != tt.wantNil {
518				t.Errorf("GetIndex() got nil = %v, want nil = %v", tree == nil, tt.wantNil)
519			}
520		})
521	}
522}
523
524func TestAddIndexPanic(t *testing.T) {
525	c := New()
526	defer func() {
527		if r := recover(); r == nil {
528			t.Error("Expected panic when adding _id index")
529		}
530	}()
531
532	c.AddIndex(IDIndex, func(v any) string {
533		return ""
534	}, DefaultIndex)
535}
536
537func TestCaseInsensitiveOperations(t *testing.T) {
538	c := New()
539	c.AddIndex("email", func(v any) string {
540		return v.(*Person).Email
541	}, UniqueIndex|CaseInsensitiveIndex)
542
543	p := &Person{Email: "[email protected]"}
544	id := c.Set(p)
545
546	tests := []struct {
547		name      string
548		key       string
549		wantObj   bool
550		operation string // "get" or "getAll"
551		wantCount int
552	}{
553		{"Get exact match", "[email protected]", true, "get", 1},
554		{"Get different case", "[email protected]", true, "get", 1},
555		{"Get non-existent", "[email protected]", false, "get", 0},
556		{"GetAll exact match", "[email protected]", true, "getAll", 1},
557		{"GetAll different case", "[email protected]", true, "getAll", 1},
558		{"GetAll non-existent", "[email protected]", false, "getAll", 0},
559	}
560
561	for _, tt := range tests {
562		t.Run(tt.name, func(t *testing.T) {
563			if tt.operation == "get" {
564				iter := c.Get("email", tt.key)
565				if iter.Empty() {
566					if tt.wantObj {
567						t.Error("Expected iterator to not be empty")
568					}
569					return
570				}
571				hasValue := iter.Next()
572				if hasValue != tt.wantObj {
573					t.Errorf("Get() got object = %v, want object = %v", hasValue, tt.wantObj)
574				}
575				if hasValue {
576					entry := iter.Value()
577					if entry.ID != seqid.ID(id).String() {
578						t.Errorf("Get() got id = %v, want id = %v", entry.ID, seqid.ID(id).String())
579					}
580				}
581			} else {
582				entries := c.GetAll("email", tt.key)
583				if len(entries) != tt.wantCount {
584					t.Errorf("GetAll() returned %d entries, want %d", len(entries), tt.wantCount)
585				}
586				if tt.wantCount > 0 {
587					entry := entries[0]
588					if entry.ID != seqid.ID(id).String() {
589						t.Errorf("GetAll() returned ID %s, want %s", entry.ID, seqid.ID(id).String())
590					}
591				}
592			}
593		})
594	}
595}
596
597func TestGetInvalidID(t *testing.T) {
598	c := New()
599	iter := c.Get(IDIndex, "not-a-valid-id")
600	if !iter.Empty() {
601		t.Errorf("Get() with invalid ID format got an entry, want nil")
602	}
603}
604
605func TestGetAll(t *testing.T) {
606	c := New()
607	c.AddIndex("tags", func(v any) []string {
608		return v.(*Person).Tags
609	}, DefaultIndex)
610	c.AddIndex("age", func(v any) string {
611		return strconv.Itoa(v.(*Person).Age)
612	}, DefaultIndex)
613	c.AddIndex("name", func(v any) string {
614		return v.(*Person).Name
615	}, UniqueIndex)
616
617	// Create test data
618	people := []*Person{
619		{Name: "Alice", Age: 30, Tags: []string{"dev", "go"}},
620		{Name: "Bob", Age: 30, Tags: []string{"dev", "python"}},
621		{Name: "Charlie", Age: 25, Tags: []string{"dev", "rust"}},
622	}
623
624	ids := make([]uint64, len(people))
625	for i, p := range people {
626		ids[i] = c.Set(p)
627		if ids[i] == 0 {
628			t.Fatalf("Failed to set person %s", p.Name)
629		}
630	}
631
632	tests := []struct {
633		name      string
634		indexName string
635		key       string
636		wantCount int
637	}{
638		{
639			name:      "Multi-value index with multiple matches",
640			indexName: "tags",
641			key:       "dev",
642			wantCount: 3,
643		},
644		{
645			name:      "Multi-value index with single match",
646			indexName: "tags",
647			key:       "go",
648			wantCount: 1,
649		},
650		{
651			name:      "Multi-value index with no matches",
652			indexName: "tags",
653			key:       "java",
654			wantCount: 0,
655		},
656		{
657			name:      "Single-value non-unique index with multiple matches",
658			indexName: "age",
659			key:       "30",
660			wantCount: 2,
661		},
662		{
663			name:      "Single-value unique index",
664			indexName: "name",
665			key:       "Alice",
666			wantCount: 1,
667		},
668		{
669			name:      "Non-existent index",
670			indexName: "invalid",
671			key:       "value",
672			wantCount: 0,
673		},
674	}
675
676	for _, tt := range tests {
677		t.Run(tt.name, func(t *testing.T) {
678			iter := c.Get(tt.indexName, tt.key)
679			count := 0
680			for iter.Next() {
681				entry := iter.Value()
682				if entry.ID == "" {
683					t.Error("Got entry with empty ID")
684				}
685				if entry.Obj == nil {
686					t.Error("Got entry with nil Obj")
687				}
688				count++
689			}
690			if count != tt.wantCount {
691				t.Errorf("Got %d entries, want %d", count, tt.wantCount)
692			}
693		})
694	}
695}
696
697func TestIndexOperations(t *testing.T) {
698	tests := []struct {
699		name    string
700		setup   func(*Collection) (uint64, error)
701		verify  func(*Collection, uint64) error
702		wantErr bool
703	}{
704		{
705			name: "Basic set and get",
706			setup: func(c *Collection) (uint64, error) {
707				c.AddIndex("name", func(v any) string {
708					return v.(*Person).Name
709				}, UniqueIndex)
710				return c.Set(&Person{Name: "Alice", Age: 30}), nil
711			},
712			verify: func(c *Collection, id uint64) error {
713				iter := c.Get(IDIndex, seqid.ID(id).String())
714				if !iter.Next() {
715					return errors.New("failed to get object by ID")
716				}
717				entry := iter.Value()
718				if entry.Obj.(*Person).Name != "Alice" {
719					return errors.New("got wrong object")
720				}
721				return nil
722			},
723		},
724		{
725			name: "Composite index",
726			setup: func(c *Collection) (uint64, error) {
727				c.AddIndex("composite", func(v any) string {
728					p := v.(*Person)
729					return p.Name + ":" + strconv.Itoa(p.Age)
730				}, UniqueIndex)
731				return c.Set(&Person{Name: "Alice", Age: 30}), nil
732			},
733			verify: func(c *Collection, id uint64) error {
734				iter := c.Get("composite", "Alice:30")
735				if !iter.Next() {
736					return errors.New("failed to get object by composite index")
737				}
738				return nil
739			},
740		},
741		// Add more test cases combining unique scenarios from original tests
742	}
743
744	for _, tt := range tests {
745		t.Run(tt.name, func(t *testing.T) {
746			c := New()
747			id, err := tt.setup(c)
748			if (err != nil) != tt.wantErr {
749				t.Errorf("setup error = %v, wantErr %v", err, tt.wantErr)
750				return
751			}
752			if err == nil {
753				if err := tt.verify(c, id); err != nil {
754					t.Errorf("verification failed: %v", err)
755				}
756			}
757		})
758	}
759}
760
761func TestMultiValueIndexes(t *testing.T) {
762	c := New()
763	c.AddIndex("tags", func(v any) []string {
764		return v.(*Person).Tags
765	}, DefaultIndex)
766
767	tests := []struct {
768		name      string
769		setup     []*Person
770		searchTag string
771		wantCount int
772	}{
773		{
774			name: "Multiple tags, multiple matches",
775			setup: []*Person{
776				{Name: "Alice", Tags: []string{"dev", "go"}},
777				{Name: "Bob", Tags: []string{"dev", "python"}},
778				{Name: "Charlie", Tags: []string{"dev", "rust"}},
779			},
780			searchTag: "dev",
781			wantCount: 3,
782		},
783		{
784			name: "Single tag match",
785			setup: []*Person{
786				{Name: "Alice", Tags: []string{"dev", "go"}},
787				{Name: "Bob", Tags: []string{"dev", "python"}},
788			},
789			searchTag: "go",
790			wantCount: 1,
791		},
792		{
793			name: "No matches",
794			setup: []*Person{
795				{Name: "Alice", Tags: []string{"dev", "go"}},
796				{Name: "Bob", Tags: []string{"dev", "python"}},
797			},
798			searchTag: "java",
799			wantCount: 0,
800		},
801		{
802			name: "Empty tags",
803			setup: []*Person{
804				{Name: "Alice", Tags: []string{}},
805				{Name: "Bob", Tags: nil},
806			},
807			searchTag: "dev",
808			wantCount: 0,
809		},
810	}
811
812	for _, tt := range tests {
813		t.Run(tt.name, func(t *testing.T) {
814			c := New()
815			c.AddIndex("tags", func(v any) []string {
816				return v.(*Person).Tags
817			}, DefaultIndex)
818
819			// Setup test data
820			for _, p := range tt.setup {
821				if id := c.Set(p); id == 0 {
822					t.Fatalf("Failed to set person %s", p.Name)
823				}
824			}
825
826			// Test Get operation
827			iter := c.Get("tags", tt.searchTag)
828			count := 0
829			for iter.Next() {
830				count++
831			}
832			if count != tt.wantCount {
833				t.Errorf("Get() got %d matches, want %d", count, tt.wantCount)
834			}
835		})
836	}
837}
838
839func TestGetOperations(t *testing.T) {
840	c := New()
841	c.AddIndex("name", func(v any) string {
842		return v.(*Person).Name
843	}, UniqueIndex)
844	c.AddIndex("age", func(v any) string {
845		return strconv.Itoa(v.(*Person).Age)
846	}, DefaultIndex)
847
848	// Setup test data
849	testPeople := []*Person{
850		{Name: "Alice", Age: 30},
851		{Name: "Bob", Age: 30},
852		{Name: "Charlie", Age: 25},
853	}
854
855	ids := make([]uint64, len(testPeople))
856	for i, p := range testPeople {
857		ids[i] = c.Set(p)
858		if ids[i] == 0 {
859			t.Fatalf("Failed to set person %s", p.Name)
860		}
861	}
862
863	tests := []struct {
864		name      string
865		indexName string
866		key       string
867		wantCount int
868		wantErr   bool
869	}{
870		{
871			name:      "Get by ID",
872			indexName: IDIndex,
873			key:       seqid.ID(ids[0]).String(),
874			wantCount: 1,
875			wantErr:   false,
876		},
877		{
878			name:      "Get by unique index",
879			indexName: "name",
880			key:       "Alice",
881			wantCount: 1,
882			wantErr:   false,
883		},
884		{
885			name:      "Get by non-unique index",
886			indexName: "age",
887			key:       "30",
888			wantCount: 2,
889			wantErr:   false,
890		},
891		{
892			name:      "Get with invalid index",
893			indexName: "invalid_index",
894			key:       "value",
895			wantCount: 0,
896			wantErr:   true,
897		},
898		{
899			name:      "Get with invalid ID format",
900			indexName: IDIndex,
901			key:       "not-a-valid-id",
902			wantCount: 0,
903			wantErr:   true,
904		},
905	}
906
907	for _, tt := range tests {
908		t.Run(tt.name, func(t *testing.T) {
909			iter := c.Get(tt.indexName, tt.key)
910			if iter.Empty() {
911				if !tt.wantErr {
912					t.Errorf("Get() returned empty iterator, wanted %d results", tt.wantCount)
913				}
914				return
915			}
916
917			count := 0
918			for iter.Next() {
919				entry := iter.Value()
920				if entry.ID == "" {
921					t.Error("Got entry with empty ID")
922				}
923				if entry.Obj == nil {
924					t.Error("Got entry with nil Obj")
925				}
926				count++
927			}
928
929			if count != tt.wantCount {
930				t.Errorf("Get() returned %d results, want %d", count, tt.wantCount)
931			}
932		})
933	}
934}
935
936func TestEntryString(t *testing.T) {
937	tests := []struct {
938		name     string
939		entry    *Entry
940		expected string
941	}{
942		{
943			name:     "Nil entry",
944			entry:    nil,
945			expected: "<nil>",
946		},
947		{
948			name: "Person entry",
949			entry: &Entry{
950				ID:  "123",
951				Obj: &Person{Name: "Alice", Age: 30},
952			},
953			expected: `Entry{ID: 123, Obj: name=Alice age=30 email= username= tags=}`,
954		},
955		{
956			name: "Entry with nil object",
957			entry: &Entry{
958				ID:  "456",
959				Obj: nil,
960			},
961			expected: `Entry{ID: 456, Obj: <nil>}`,
962		},
963		{
964			name: "Entry with complete person",
965			entry: &Entry{
966				ID: "789",
967				Obj: &Person{
968					Name:     "Bob",
969					Age:      25,
970					Email:    "[email protected]",
971					Username: "bobby",
972					Tags:     []string{"dev", "go"},
973				},
974			},
975			expected: `Entry{ID: 789, Obj: name=Bob age=25 [email protected] username=bobby tags=dev,go}`,
976		},
977	}
978
979	for _, tt := range tests {
980		t.Run(tt.name, func(t *testing.T) {
981			got := tt.entry.String()
982			if got != tt.expected {
983				t.Errorf("Entry.String() = %q, want %q", got, tt.expected)
984			}
985		})
986	}
987}