collection_test.gno

20.94 Kb ยท 987 lines
  1package collection
  2
  3import (
  4	"errors"
  5	"strconv"
  6	"strings"
  7	"testing"
  8
  9	"gno.land/p/demo/seqid"
 10	"gno.land/p/demo/ufmt"
 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: "alice@test.com"}
 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: "test@example.com"})
346				return c.Set(&Person{Email: "TEST@EXAMPLE.COM"}) // 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: "alice@test.com"},
423			wantID: true,
424		},
425		{
426			name:   "Populated optional field",
427			person: &Person{Name: "Bob", Email: "bob@test.com", 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: "Test@Example.com"}
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", "Test@Example.com", true, "get", 1},
554		{"Get different case", "test@example.COM", true, "get", 1},
555		{"Get non-existent", "other@example.com", false, "get", 0},
556		{"GetAll exact match", "Test@Example.com", true, "getAll", 1},
557		{"GetAll different case", "test@example.COM", true, "getAll", 1},
558		{"GetAll non-existent", "other@example.com", 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:    "bob@example.com",
971					Username: "bobby",
972					Tags:     []string{"dev", "go"},
973				},
974			},
975			expected: `Entry{ID: 789, Obj: name=Bob age=25 email=bob@example.com 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}