master
go 476 lines 15.6 KB
Raw
1 package mg16
2
3 import (
4 "bytes"
5 "encoding/json"
6 "maps"
7 "os"
8 "path/filepath"
9 "testing"
10
11 "github.com/ipfs/kubo/repo/fsrepo/migrations/common"
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14 )
15
16 // Helper function to run migration on JSON input and return result
17 func runMigrationOnJSON(t *testing.T, input string) map[string]any {
18 t.Helper()
19 var output bytes.Buffer
20 err := convert(bytes.NewReader([]byte(input)), &output)
21 require.NoError(t, err)
22
23 var result map[string]any
24 err = json.Unmarshal(output.Bytes(), &result)
25 require.NoError(t, err)
26
27 return result
28 }
29
30 // Helper function to assert nested map key has expected value
31 func assertMapKeyEquals(t *testing.T, result map[string]any, path []string, key string, expected any) {
32 t.Helper()
33 current := result
34 for _, p := range path {
35 section, exists := current[p]
36 require.True(t, exists, "Section %s not found in path %v", p, path)
37 current = section.(map[string]any)
38 }
39
40 assert.Equal(t, expected, current[key], "Expected %s to be %v", key, expected)
41 }
42
43 // Helper function to assert slice contains expected values
44 func assertSliceEquals(t *testing.T, result map[string]any, path []string, expected []string) {
45 t.Helper()
46 current := result
47 for i, p := range path[:len(path)-1] {
48 section, exists := current[p]
49 require.True(t, exists, "Section %s not found in path %v at index %d", p, path, i)
50 current = section.(map[string]any)
51 }
52
53 sliceKey := path[len(path)-1]
54 slice, exists := current[sliceKey]
55 require.True(t, exists, "Slice %s not found", sliceKey)
56
57 actualSlice := slice.([]any)
58 require.Equal(t, len(expected), len(actualSlice), "Expected slice length %d, got %d", len(expected), len(actualSlice))
59
60 for i, exp := range expected {
61 assert.Equal(t, exp, actualSlice[i], "Expected slice[%d] to be %s", i, exp)
62 }
63 }
64
65 // Helper to build test config JSON with specified fields
66 func buildTestConfig(fields map[string]any) string {
67 config := map[string]any{
68 "Identity": map[string]any{"PeerID": "QmTest"},
69 }
70 maps.Copy(config, fields)
71 data, _ := json.MarshalIndent(config, "", " ")
72 return string(data)
73 }
74
75 // Helper to run migration and get DNS resolvers
76 func runMigrationAndGetDNSResolvers(t *testing.T, input string) map[string]any {
77 t.Helper()
78 result := runMigrationOnJSON(t, input)
79 dns := result["DNS"].(map[string]any)
80 return dns["Resolvers"].(map[string]any)
81 }
82
83 // Helper to assert multiple resolver values
84 func assertResolvers(t *testing.T, resolvers map[string]any, expected map[string]string) {
85 t.Helper()
86 for key, expectedValue := range expected {
87 assert.Equal(t, expectedValue, resolvers[key], "Expected %s resolver to be %v", key, expectedValue)
88 }
89 }
90
91 // =============================================================================
92 // End-to-End Migration Tests
93 // =============================================================================
94
95 func TestMigration(t *testing.T) {
96 // Create a temporary directory for testing
97 tempDir, err := os.MkdirTemp("", "migration-test-16-to-17")
98 require.NoError(t, err)
99 defer os.RemoveAll(tempDir)
100
101 // Create a test config with default bootstrap peers
102 testConfig := map[string]any{
103 "Bootstrap": []string{
104 "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
105 "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
106 "/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer", // Custom peer
107 },
108 "DNS": map[string]any{
109 "Resolvers": map[string]string{},
110 },
111 "Routing": map[string]any{
112 "DelegatedRouters": []string{},
113 },
114 "Ipns": map[string]any{
115 "ResolveCacheSize": 128,
116 },
117 "Identity": map[string]any{
118 "PeerID": "QmTest",
119 },
120 "Version": map[string]any{
121 "Current": "0.36.0",
122 },
123 }
124
125 // Write test config
126 configPath := filepath.Join(tempDir, "config")
127 configData, err := json.MarshalIndent(testConfig, "", " ")
128 require.NoError(t, err)
129 err = os.WriteFile(configPath, configData, 0644)
130 require.NoError(t, err)
131
132 // Create version file
133 versionPath := filepath.Join(tempDir, "version")
134 err = os.WriteFile(versionPath, []byte("16"), 0644)
135 require.NoError(t, err)
136
137 // Run migration
138 opts := common.Options{
139 Path: tempDir,
140 Verbose: true,
141 }
142
143 err = Migration.Apply(opts)
144 require.NoError(t, err)
145
146 // Verify version was updated
147 versionData, err := os.ReadFile(versionPath)
148 require.NoError(t, err)
149 assert.Equal(t, "17", string(versionData), "Expected version 17")
150
151 // Verify config was updated
152 configData, err = os.ReadFile(configPath)
153 require.NoError(t, err)
154
155 var updatedConfig map[string]any
156 err = json.Unmarshal(configData, &updatedConfig)
157 require.NoError(t, err)
158
159 // Check AutoConf was added
160 autoConf, exists := updatedConfig["AutoConf"]
161 assert.True(t, exists, "AutoConf section not added")
162 autoConfMap := autoConf.(map[string]any)
163 // URL is not set explicitly in migration (uses implicit default)
164 _, hasURL := autoConfMap["URL"]
165 assert.False(t, hasURL, "AutoConf URL should not be explicitly set in migration")
166
167 // Check Bootstrap was updated
168 bootstrap := updatedConfig["Bootstrap"].([]any)
169 assert.Equal(t, 2, len(bootstrap), "Expected 2 bootstrap entries")
170 assert.Equal(t, "auto", bootstrap[0], "Expected first bootstrap entry to be 'auto'")
171 assert.Equal(t, "/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer", bootstrap[1], "Expected custom peer to be preserved")
172
173 // Check DNS.Resolvers was updated
174 dns := updatedConfig["DNS"].(map[string]any)
175 resolvers := dns["Resolvers"].(map[string]any)
176 assert.Equal(t, "auto", resolvers["."], "Expected DNS resolver for '.' to be 'auto'")
177
178 // Check Routing.DelegatedRouters was updated
179 routing := updatedConfig["Routing"].(map[string]any)
180 delegatedRouters := routing["DelegatedRouters"].([]any)
181 assert.Equal(t, 1, len(delegatedRouters))
182 assert.Equal(t, "auto", delegatedRouters[0], "Expected DelegatedRouters to be ['auto']")
183
184 // Check Ipns.DelegatedPublishers was updated
185 ipns := updatedConfig["Ipns"].(map[string]any)
186 delegatedPublishers := ipns["DelegatedPublishers"].([]any)
187 assert.Equal(t, 1, len(delegatedPublishers))
188 assert.Equal(t, "auto", delegatedPublishers[0], "Expected DelegatedPublishers to be ['auto']")
189
190 // Test revert
191 err = Migration.Revert(opts)
192 require.NoError(t, err)
193
194 // Verify version was reverted
195 versionData, err = os.ReadFile(versionPath)
196 require.NoError(t, err)
197 assert.Equal(t, "16", string(versionData), "Expected version 16 after revert")
198 }
199
200 func TestConvert(t *testing.T) {
201 t.Parallel()
202 input := buildTestConfig(map[string]any{
203 "Bootstrap": []string{
204 "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
205 "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
206 },
207 })
208
209 result := runMigrationOnJSON(t, input)
210
211 // Check that AutoConf section was added but is empty (using implicit defaults)
212 autoConf, exists := result["AutoConf"]
213 require.True(t, exists, "AutoConf section should exist")
214 autoConfMap, ok := autoConf.(map[string]any)
215 require.True(t, ok, "AutoConf should be a map")
216 require.Empty(t, autoConfMap, "AutoConf should be empty (using implicit defaults)")
217
218 // Check that Bootstrap was updated to "auto"
219 assertSliceEquals(t, result, []string{"Bootstrap"}, []string{"auto"})
220 }
221
222 // =============================================================================
223 // Bootstrap Migration Tests
224 // =============================================================================
225
226 func TestBootstrapMigration(t *testing.T) {
227 t.Parallel()
228
229 t.Run("process bootstrap peers logic verification", func(t *testing.T) {
230 t.Parallel()
231 tests := []struct {
232 name string
233 peers []string
234 expected []string
235 }{
236 {
237 name: "empty peers",
238 peers: []string{},
239 expected: []string{"auto"},
240 },
241 {
242 name: "only default peers",
243 peers: []string{
244 "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
245 "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
246 },
247 expected: []string{"auto"},
248 },
249 {
250 name: "mixed default and custom peers",
251 peers: []string{
252 "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
253 "/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer",
254 },
255 expected: []string{"auto", "/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer"},
256 },
257 {
258 name: "only custom peers",
259 peers: []string{
260 "/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer1",
261 "/ip4/192.168.1.2/tcp/4001/p2p/QmCustomPeer2",
262 },
263 expected: []string{
264 "/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer1",
265 "/ip4/192.168.1.2/tcp/4001/p2p/QmCustomPeer2",
266 },
267 },
268 }
269
270 for _, tt := range tests {
271 t.Run(tt.name, func(t *testing.T) {
272 t.Parallel()
273 result := processBootstrapPeers(tt.peers)
274 require.Equal(t, len(tt.expected), len(result), "Expected %d peers, got %d", len(tt.expected), len(result))
275 for i, expected := range tt.expected {
276 assert.Equal(t, expected, result[i], "Expected peer %d to be %s", i, expected)
277 }
278 })
279 }
280 })
281
282 t.Run("replaces all old default bootstrapper peers with auto entry", func(t *testing.T) {
283 t.Parallel()
284 input := buildTestConfig(map[string]any{
285 "Bootstrap": []string{
286 "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
287 "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
288 "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
289 "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
290 "/dnsaddr/va1.bootstrap.libp2p.io/p2p/12D3KooWKnDdG3iXw9eTFijk3EWSunZcFi54Zka4wmtqtt6rPxc8",
291 "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
292 "/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
293 },
294 })
295
296 result := runMigrationOnJSON(t, input)
297 assertSliceEquals(t, result, []string{"Bootstrap"}, []string{"auto"})
298 })
299
300 t.Run("creates Bootstrap section with auto when missing", func(t *testing.T) {
301 t.Parallel()
302 input := `{"Identity": {"PeerID": "QmTest"}}`
303 result := runMigrationOnJSON(t, input)
304 assertSliceEquals(t, result, []string{"Bootstrap"}, []string{"auto"})
305 })
306 }
307
308 // =============================================================================
309 // DNS Migration Tests
310 // =============================================================================
311
312 func TestDNSMigration(t *testing.T) {
313 t.Parallel()
314
315 t.Run("creates DNS section with auto resolver when missing", func(t *testing.T) {
316 t.Parallel()
317 input := `{"Identity": {"PeerID": "QmTest"}}`
318 result := runMigrationOnJSON(t, input)
319 assertMapKeyEquals(t, result, []string{"DNS", "Resolvers"}, ".", "auto")
320 })
321
322 t.Run("preserves all custom DNS resolvers unchanged", func(t *testing.T) {
323 t.Parallel()
324 input := buildTestConfig(map[string]any{
325 "DNS": map[string]any{
326 "Resolvers": map[string]string{
327 ".": "https://my-custom-resolver.com",
328 ".eth": "https://eth.resolver",
329 },
330 },
331 })
332
333 resolvers := runMigrationAndGetDNSResolvers(t, input)
334 assertResolvers(t, resolvers, map[string]string{
335 ".": "https://my-custom-resolver.com",
336 ".eth": "https://eth.resolver",
337 })
338 })
339
340 t.Run("preserves custom dot and eth resolvers unchanged", func(t *testing.T) {
341 t.Parallel()
342 input := buildTestConfig(map[string]any{
343 "DNS": map[string]any{
344 "Resolvers": map[string]string{
345 ".": "https://cloudflare-dns.com/dns-query",
346 ".eth": "https://example.com/dns-query",
347 },
348 },
349 })
350
351 resolvers := runMigrationAndGetDNSResolvers(t, input)
352 assertResolvers(t, resolvers, map[string]string{
353 ".": "https://cloudflare-dns.com/dns-query",
354 ".eth": "https://example.com/dns-query",
355 })
356 })
357
358 t.Run("replaces old default eth resolver with auto", func(t *testing.T) {
359 t.Parallel()
360 input := buildTestConfig(map[string]any{
361 "DNS": map[string]any{
362 "Resolvers": map[string]string{
363 ".": "https://cloudflare-dns.com/dns-query",
364 ".eth": "https://dns.eth.limo/dns-query", // should be replaced
365 ".crypto": "https://resolver.cloudflare-eth.com/dns-query", // should be replaced
366 ".link": "https://dns.eth.link/dns-query", // should be replaced
367 },
368 },
369 })
370
371 resolvers := runMigrationAndGetDNSResolvers(t, input)
372 assertResolvers(t, resolvers, map[string]string{
373 ".": "https://cloudflare-dns.com/dns-query", // preserved
374 ".eth": "auto", // replaced
375 ".crypto": "auto", // replaced
376 ".link": "auto", // replaced
377 })
378 })
379 }
380
381 // =============================================================================
382 // Routing Migration Tests
383 // =============================================================================
384
385 func TestRoutingMigration(t *testing.T) {
386 t.Parallel()
387
388 t.Run("creates Routing section with auto DelegatedRouters when missing", func(t *testing.T) {
389 t.Parallel()
390 input := `{"Identity": {"PeerID": "QmTest"}}`
391 result := runMigrationOnJSON(t, input)
392 assertSliceEquals(t, result, []string{"Routing", "DelegatedRouters"}, []string{"auto"})
393 })
394
395 t.Run("replaces cid.contact with auto while preserving custom routers added by user", func(t *testing.T) {
396 t.Parallel()
397 input := buildTestConfig(map[string]any{
398 "Routing": map[string]any{
399 "DelegatedRouters": []string{
400 "https://cid.contact",
401 "https://my-custom-router.com",
402 },
403 },
404 })
405
406 result := runMigrationOnJSON(t, input)
407 assertSliceEquals(t, result, []string{"Routing", "DelegatedRouters"}, []string{"auto", "https://my-custom-router.com"})
408 })
409 }
410
411 // =============================================================================
412 // IPNS Migration Tests
413 // =============================================================================
414
415 func TestIpnsMigration(t *testing.T) {
416 t.Parallel()
417
418 t.Run("creates Ipns section with auto DelegatedPublishers when missing", func(t *testing.T) {
419 t.Parallel()
420 input := `{"Identity": {"PeerID": "QmTest"}}`
421 result := runMigrationOnJSON(t, input)
422 assertSliceEquals(t, result, []string{"Ipns", "DelegatedPublishers"}, []string{"auto"})
423 })
424
425 t.Run("preserves existing custom DelegatedPublishers unchanged", func(t *testing.T) {
426 t.Parallel()
427 input := buildTestConfig(map[string]any{
428 "Ipns": map[string]any{
429 "DelegatedPublishers": []string{
430 "https://my-publisher.com",
431 "https://another-publisher.com",
432 },
433 },
434 })
435
436 result := runMigrationOnJSON(t, input)
437 assertSliceEquals(t, result, []string{"Ipns", "DelegatedPublishers"}, []string{"https://my-publisher.com", "https://another-publisher.com"})
438 })
439
440 t.Run("adds auto DelegatedPublishers to existing Ipns section", func(t *testing.T) {
441 t.Parallel()
442 input := buildTestConfig(map[string]any{
443 "Ipns": map[string]any{
444 "ResolveCacheSize": 128,
445 },
446 })
447
448 result := runMigrationOnJSON(t, input)
449 assertMapKeyEquals(t, result, []string{"Ipns"}, "ResolveCacheSize", float64(128))
450 assertSliceEquals(t, result, []string{"Ipns", "DelegatedPublishers"}, []string{"auto"})
451 })
452 }
453
454 // =============================================================================
455 // AutoConf Migration Tests
456 // =============================================================================
457
458 func TestAutoConfMigration(t *testing.T) {
459 t.Parallel()
460
461 t.Run("preserves existing AutoConf fields unchanged", func(t *testing.T) {
462 t.Parallel()
463 input := buildTestConfig(map[string]any{
464 "AutoConf": map[string]any{
465 "URL": "https://custom.example.com/autoconf.json",
466 "Enabled": false,
467 "CustomField": "preserved",
468 },
469 })
470
471 result := runMigrationOnJSON(t, input)
472 assertMapKeyEquals(t, result, []string{"AutoConf"}, "URL", "https://custom.example.com/autoconf.json")
473 assertMapKeyEquals(t, result, []string{"AutoConf"}, "Enabled", false)
474 assertMapKeyEquals(t, result, []string{"AutoConf"}, "CustomField", "preserved")
475 })
476 }