rotree_test.gno
5.43 Kb · 221 lines
1package rotree
2
3import (
4 "testing"
5
6 "gno.land/p/nt/bptree/v0"
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 := bptree.NewBPTree32()
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 := roTree.Get("alice")
53 if value == nil {
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 := bptree.NewBPTree32()
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 }{
131 {"ExistingKey1", "key1", "value1_readonly"},
132 {"ExistingKey2", "key2", "value2_readonly"},
133 {"NonExistingKey", "key4", nil},
134 }
135
136 for _, tt := range tests {
137 t.Run(tt.name, func(t *testing.T) {
138 value := roTree.Get(tt.key)
139 if value != tt.expected {
140 t.Errorf("For key %s, expected %v, got %v", tt.key, tt.expected, value)
141 }
142 })
143 }
144}
145
146// Add example tests showing different makeEntrySafeFn implementations
147func TestMakeEntrySafeFnVariants(t *testing.T) {
148 tree := bptree.NewBPTree32()
149 tree.Set("slice", []int{1, 2, 3})
150 tree.Set("map", map[string]int{"a": 1})
151
152 tests := []struct {
153 name string
154 makeEntrySafeFn func(any) any
155 key string
156 validate func(t *testing.T, value any)
157 }{
158 {
159 name: "Defensive Copy Slice",
160 makeEntrySafeFn: func(v any) any {
161 original := v.([]int)
162 return append([]int{}, original...)
163 },
164 key: "slice",
165 validate: func(t *testing.T, value any) {
166 slice := value.([]int)
167 // Modify the returned slice
168 slice[0] = 999
169 // Verify original is unchanged
170 originalValue := tree.Get("slice")
171 original := originalValue.([]int)
172 if original[0] != 1 {
173 t.Error("Original slice was modified")
174 }
175 },
176 },
177 // Add more test cases for different makeEntrySafeFn implementations
178 }
179
180 for _, tt := range tests {
181 t.Run(tt.name, func(t *testing.T) {
182 roTree := Wrap(tree, tt.makeEntrySafeFn)
183 value := roTree.Get(tt.key)
184 if value == nil {
185 t.Fatal("Key not found")
186 }
187 tt.validate(t, value)
188 })
189 }
190}
191
192func TestNilMakeEntrySafeFn(t *testing.T) {
193 // Create a tree with some test data
194 tree := bptree.NewBPTree32()
195 originalValue := []int{1, 2, 3}
196 tree.Set("test", originalValue)
197
198 // Create a ReadOnlyTree with nil makeEntrySafeFn
199 roTree := Wrap(tree, nil)
200
201 // Test that we get back the original value
202 value := roTree.Get("test")
203 if value == nil {
204 t.Fatal("Key not found")
205 }
206
207 // Verify it's the exact same slice (not a copy)
208 retrievedSlice := value.([]int)
209 if &retrievedSlice[0] != &originalValue[0] {
210 t.Error("Expected to get back the original slice reference")
211 }
212
213 // Test through iteration as well
214 roTree.Iterate("", "", func(key string, value any) bool {
215 retrievedSlice := value.([]int)
216 if &retrievedSlice[0] != &originalValue[0] {
217 t.Error("Expected to get back the original slice reference in iteration")
218 }
219 return false
220 })
221}