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}