rotree_test.gno

5.53 Kb ยท 222 lines
  1package rotree
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/demo/avl"
  7)
  8
  9func TestExample(t *testing.T) {
 10	// User represents our internal data structure
 11	type User struct {
 12		ID       string
 13		Name     string
 14		Balance  int
 15		Internal string // sensitive internal data
 16	}
 17
 18	// Create and populate the original tree with user pointers
 19	tree := avl.NewTree()
 20	tree.Set("alice", &User{
 21		ID:       "1",
 22		Name:     "Alice",
 23		Balance:  100,
 24		Internal: "sensitive_data_1",
 25	})
 26	tree.Set("bob", &User{
 27		ID:       "2",
 28		Name:     "Bob",
 29		Balance:  200,
 30		Internal: "sensitive_data_2",
 31	})
 32
 33	// Define a makeEntrySafeFn that:
 34	// 1. Creates a defensive copy of the User struct
 35	// 2. Omits sensitive internal data
 36	makeEntrySafeFn := func(v any) any {
 37		originalUser := v.(*User)
 38		return &User{
 39			ID:       originalUser.ID,
 40			Name:     originalUser.Name,
 41			Balance:  originalUser.Balance,
 42			Internal: "", // Omit sensitive data
 43		}
 44	}
 45
 46	// Create a read-only view of the tree
 47	roTree := Wrap(tree, makeEntrySafeFn)
 48
 49	// Test retrieving and verifying a user
 50	t.Run("Get User", func(t *testing.T) {
 51		// Get user from read-only tree
 52		value, exists := roTree.Get("alice")
 53		if !exists {
 54			t.Fatal("User 'alice' not found")
 55		}
 56
 57		user := value.(*User)
 58
 59		// Verify user data is correct
 60		if user.Name != "Alice" || user.Balance != 100 {
 61			t.Errorf("Unexpected user data: got name=%s balance=%d", user.Name, user.Balance)
 62		}
 63
 64		// Verify sensitive data is not exposed
 65		if user.Internal != "" {
 66			t.Error("Sensitive data should not be exposed")
 67		}
 68
 69		// Verify it's a different instance than the original
 70		originalValue, _ := tree.Get("alice")
 71		originalUser := originalValue.(*User)
 72		if user == originalUser {
 73			t.Error("Read-only tree should return a copy, not the original pointer")
 74		}
 75	})
 76
 77	// Test iterating over users
 78	t.Run("Iterate Users", func(t *testing.T) {
 79		count := 0
 80		roTree.Iterate("", "", func(key string, value any) bool {
 81			user := value.(*User)
 82			// Verify each user has empty Internal field
 83			if user.Internal != "" {
 84				t.Error("Sensitive data exposed during iteration")
 85			}
 86			count++
 87			return false
 88		})
 89
 90		if count != 2 {
 91			t.Errorf("Expected 2 users, got %d", count)
 92		}
 93	})
 94
 95	// Verify that modifications to the returned user don't affect the original
 96	t.Run("Modification Safety", func(t *testing.T) {
 97		value, _ := roTree.Get("alice")
 98		user := value.(*User)
 99
100		// Try to modify the returned user
101		user.Balance = 999
102		user.Internal = "hacked"
103
104		// Verify original is unchanged
105		originalValue, _ := tree.Get("alice")
106		originalUser := originalValue.(*User)
107		if originalUser.Balance != 100 || originalUser.Internal != "sensitive_data_1" {
108			t.Error("Original user data was modified")
109		}
110	})
111}
112
113func TestReadOnlyTree(t *testing.T) {
114	// Example of a makeEntrySafeFn that appends "_readonly" to demonstrate transformation
115	makeEntrySafeFn := func(value any) any {
116		return value.(string) + "_readonly"
117	}
118
119	tree := avl.NewTree()
120	tree.Set("key1", "value1")
121	tree.Set("key2", "value2")
122	tree.Set("key3", "value3")
123
124	roTree := Wrap(tree, makeEntrySafeFn)
125
126	tests := []struct {
127		name     string
128		key      string
129		expected any
130		exists   bool
131	}{
132		{"ExistingKey1", "key1", "value1_readonly", true},
133		{"ExistingKey2", "key2", "value2_readonly", true},
134		{"NonExistingKey", "key4", nil, false},
135	}
136
137	for _, tt := range tests {
138		t.Run(tt.name, func(t *testing.T) {
139			value, exists := roTree.Get(tt.key)
140			if exists != tt.exists || value != tt.expected {
141				t.Errorf("For key %s, expected %v (exists: %v), got %v (exists: %v)", tt.key, tt.expected, tt.exists, value, exists)
142			}
143		})
144	}
145}
146
147// Add example tests showing different makeEntrySafeFn implementations
148func TestMakeEntrySafeFnVariants(t *testing.T) {
149	tree := avl.NewTree()
150	tree.Set("slice", []int{1, 2, 3})
151	tree.Set("map", map[string]int{"a": 1})
152
153	tests := []struct {
154		name            string
155		makeEntrySafeFn func(any) any
156		key             string
157		validate        func(t *testing.T, value any)
158	}{
159		{
160			name: "Defensive Copy Slice",
161			makeEntrySafeFn: func(v any) any {
162				original := v.([]int)
163				return append([]int{}, original...)
164			},
165			key: "slice",
166			validate: func(t *testing.T, value any) {
167				slice := value.([]int)
168				// Modify the returned slice
169				slice[0] = 999
170				// Verify original is unchanged
171				originalValue, _ := tree.Get("slice")
172				original := originalValue.([]int)
173				if original[0] != 1 {
174					t.Error("Original slice was modified")
175				}
176			},
177		},
178		// Add more test cases for different makeEntrySafeFn implementations
179	}
180
181	for _, tt := range tests {
182		t.Run(tt.name, func(t *testing.T) {
183			roTree := Wrap(tree, tt.makeEntrySafeFn)
184			value, exists := roTree.Get(tt.key)
185			if !exists {
186				t.Fatal("Key not found")
187			}
188			tt.validate(t, value)
189		})
190	}
191}
192
193func TestNilMakeEntrySafeFn(t *testing.T) {
194	// Create a tree with some test data
195	tree := avl.NewTree()
196	originalValue := []int{1, 2, 3}
197	tree.Set("test", originalValue)
198
199	// Create a ReadOnlyTree with nil makeEntrySafeFn
200	roTree := Wrap(tree, nil)
201
202	// Test that we get back the original value
203	value, exists := roTree.Get("test")
204	if !exists {
205		t.Fatal("Key not found")
206	}
207
208	// Verify it's the exact same slice (not a copy)
209	retrievedSlice := value.([]int)
210	if &retrievedSlice[0] != &originalValue[0] {
211		t.Error("Expected to get back the original slice reference")
212	}
213
214	// Test through iteration as well
215	roTree.Iterate("", "", func(key string, value any) bool {
216		retrievedSlice := value.([]int)
217		if &retrievedSlice[0] != &originalValue[0] {
218			t.Error("Expected to get back the original slice reference in iteration")
219		}
220		return false
221	})
222}