mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 10:27:46 +08:00
Some checks failed
CodeQL / codeql (push) Has been cancelled
Docker Build / docker-build (push) Has been cancelled
Gateway Conformance / gateway-conformance (push) Has been cancelled
Gateway Conformance / gateway-conformance-libp2p-experiment (push) Has been cancelled
Go Build / go-build (push) Has been cancelled
Go Check / go-check (push) Has been cancelled
Go Lint / go-lint (push) Has been cancelled
Go Test / go-test (push) Has been cancelled
Interop / interop-prep (push) Has been cancelled
Sharness / sharness-test (push) Has been cancelled
Spell Check / spellcheck (push) Has been cancelled
Interop / helia-interop (push) Has been cancelled
Interop / ipfs-webui (push) Has been cancelled
https://github.com/ipfs/kubo/pull/10883 https://github.com/ipshipyard/config.ipfs-mainnet.org/issues/3 --------- Co-authored-by: gammazero <gammazero@users.noreply.github.com>
480 lines
16 KiB
Go
480 lines
16 KiB
Go
package mg16
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Helper function to run migration on JSON input and return result
|
|
func runMigrationOnJSON(t *testing.T, input string) map[string]interface{} {
|
|
t.Helper()
|
|
var output bytes.Buffer
|
|
// Use t.TempDir() for test isolation and parallel execution support
|
|
tempDir := t.TempDir()
|
|
err := convert(bytes.NewReader([]byte(input)), &output, tempDir)
|
|
require.NoError(t, err)
|
|
|
|
var result map[string]interface{}
|
|
err = json.Unmarshal(output.Bytes(), &result)
|
|
require.NoError(t, err)
|
|
|
|
return result
|
|
}
|
|
|
|
// Helper function to assert nested map key has expected value
|
|
func assertMapKeyEquals(t *testing.T, result map[string]interface{}, path []string, key string, expected interface{}) {
|
|
t.Helper()
|
|
current := result
|
|
for _, p := range path {
|
|
section, exists := current[p]
|
|
require.True(t, exists, "Section %s not found in path %v", p, path)
|
|
current = section.(map[string]interface{})
|
|
}
|
|
|
|
assert.Equal(t, expected, current[key], "Expected %s to be %v", key, expected)
|
|
}
|
|
|
|
// Helper function to assert slice contains expected values
|
|
func assertSliceEquals(t *testing.T, result map[string]interface{}, path []string, expected []string) {
|
|
t.Helper()
|
|
current := result
|
|
for i, p := range path[:len(path)-1] {
|
|
section, exists := current[p]
|
|
require.True(t, exists, "Section %s not found in path %v at index %d", p, path, i)
|
|
current = section.(map[string]interface{})
|
|
}
|
|
|
|
sliceKey := path[len(path)-1]
|
|
slice, exists := current[sliceKey]
|
|
require.True(t, exists, "Slice %s not found", sliceKey)
|
|
|
|
actualSlice := slice.([]interface{})
|
|
require.Equal(t, len(expected), len(actualSlice), "Expected slice length %d, got %d", len(expected), len(actualSlice))
|
|
|
|
for i, exp := range expected {
|
|
assert.Equal(t, exp, actualSlice[i], "Expected slice[%d] to be %s", i, exp)
|
|
}
|
|
}
|
|
|
|
// Helper to build test config JSON with specified fields
|
|
func buildTestConfig(fields map[string]interface{}) string {
|
|
config := map[string]interface{}{
|
|
"Identity": map[string]interface{}{"PeerID": "QmTest"},
|
|
}
|
|
for k, v := range fields {
|
|
config[k] = v
|
|
}
|
|
data, _ := json.MarshalIndent(config, "", " ")
|
|
return string(data)
|
|
}
|
|
|
|
// Helper to run migration and get DNS resolvers
|
|
func runMigrationAndGetDNSResolvers(t *testing.T, input string) map[string]interface{} {
|
|
t.Helper()
|
|
result := runMigrationOnJSON(t, input)
|
|
dns := result["DNS"].(map[string]interface{})
|
|
return dns["Resolvers"].(map[string]interface{})
|
|
}
|
|
|
|
// Helper to assert multiple resolver values
|
|
func assertResolvers(t *testing.T, resolvers map[string]interface{}, expected map[string]string) {
|
|
t.Helper()
|
|
for key, expectedValue := range expected {
|
|
assert.Equal(t, expectedValue, resolvers[key], "Expected %s resolver to be %v", key, expectedValue)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// End-to-End Migration Tests
|
|
// =============================================================================
|
|
|
|
func TestMigration(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir, err := os.MkdirTemp("", "migration-test-16-to-17")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Create a test config with default bootstrap peers
|
|
testConfig := map[string]interface{}{
|
|
"Bootstrap": []string{
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
|
|
"/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer", // Custom peer
|
|
},
|
|
"DNS": map[string]interface{}{
|
|
"Resolvers": map[string]string{},
|
|
},
|
|
"Routing": map[string]interface{}{
|
|
"DelegatedRouters": []string{},
|
|
},
|
|
"Ipns": map[string]interface{}{
|
|
"ResolveCacheSize": 128,
|
|
},
|
|
"Identity": map[string]interface{}{
|
|
"PeerID": "QmTest",
|
|
},
|
|
"Version": map[string]interface{}{
|
|
"Current": "0.36.0",
|
|
},
|
|
}
|
|
|
|
// Write test config
|
|
configPath := filepath.Join(tempDir, "config")
|
|
configData, err := json.MarshalIndent(testConfig, "", " ")
|
|
require.NoError(t, err)
|
|
err = os.WriteFile(configPath, configData, 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Create version file
|
|
versionPath := filepath.Join(tempDir, "version")
|
|
err = os.WriteFile(versionPath, []byte("16"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Run migration
|
|
migration := &Migration{}
|
|
opts := Options{
|
|
Path: tempDir,
|
|
Verbose: true,
|
|
}
|
|
|
|
err = migration.Apply(opts)
|
|
require.NoError(t, err)
|
|
|
|
// Verify version was updated
|
|
versionData, err := os.ReadFile(versionPath)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "17", string(versionData), "Expected version 17")
|
|
|
|
// Verify config was updated
|
|
configData, err = os.ReadFile(configPath)
|
|
require.NoError(t, err)
|
|
|
|
var updatedConfig map[string]interface{}
|
|
err = json.Unmarshal(configData, &updatedConfig)
|
|
require.NoError(t, err)
|
|
|
|
// Check AutoConf was added
|
|
autoConf, exists := updatedConfig["AutoConf"]
|
|
assert.True(t, exists, "AutoConf section not added")
|
|
autoConfMap := autoConf.(map[string]interface{})
|
|
// URL is not set explicitly in migration (uses implicit default)
|
|
_, hasURL := autoConfMap["URL"]
|
|
assert.False(t, hasURL, "AutoConf URL should not be explicitly set in migration")
|
|
|
|
// Check Bootstrap was updated
|
|
bootstrap := updatedConfig["Bootstrap"].([]interface{})
|
|
assert.Equal(t, 2, len(bootstrap), "Expected 2 bootstrap entries")
|
|
assert.Equal(t, "auto", bootstrap[0], "Expected first bootstrap entry to be 'auto'")
|
|
assert.Equal(t, "/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer", bootstrap[1], "Expected custom peer to be preserved")
|
|
|
|
// Check DNS.Resolvers was updated
|
|
dns := updatedConfig["DNS"].(map[string]interface{})
|
|
resolvers := dns["Resolvers"].(map[string]interface{})
|
|
assert.Equal(t, "auto", resolvers["."], "Expected DNS resolver for '.' to be 'auto'")
|
|
|
|
// Check Routing.DelegatedRouters was updated
|
|
routing := updatedConfig["Routing"].(map[string]interface{})
|
|
delegatedRouters := routing["DelegatedRouters"].([]interface{})
|
|
assert.Equal(t, 1, len(delegatedRouters))
|
|
assert.Equal(t, "auto", delegatedRouters[0], "Expected DelegatedRouters to be ['auto']")
|
|
|
|
// Check Ipns.DelegatedPublishers was updated
|
|
ipns := updatedConfig["Ipns"].(map[string]interface{})
|
|
delegatedPublishers := ipns["DelegatedPublishers"].([]interface{})
|
|
assert.Equal(t, 1, len(delegatedPublishers))
|
|
assert.Equal(t, "auto", delegatedPublishers[0], "Expected DelegatedPublishers to be ['auto']")
|
|
|
|
// Test revert
|
|
err = migration.Revert(opts)
|
|
require.NoError(t, err)
|
|
|
|
// Verify version was reverted
|
|
versionData, err = os.ReadFile(versionPath)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "16", string(versionData), "Expected version 16 after revert")
|
|
}
|
|
|
|
func TestConvert(t *testing.T) {
|
|
t.Parallel()
|
|
input := buildTestConfig(map[string]interface{}{
|
|
"Bootstrap": []string{
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
|
|
},
|
|
})
|
|
|
|
result := runMigrationOnJSON(t, input)
|
|
|
|
// Check that AutoConf section was added but is empty (using implicit defaults)
|
|
autoConf, exists := result["AutoConf"]
|
|
require.True(t, exists, "AutoConf section should exist")
|
|
autoConfMap, ok := autoConf.(map[string]interface{})
|
|
require.True(t, ok, "AutoConf should be a map")
|
|
require.Empty(t, autoConfMap, "AutoConf should be empty (using implicit defaults)")
|
|
|
|
// Check that Bootstrap was updated to "auto"
|
|
assertSliceEquals(t, result, []string{"Bootstrap"}, []string{"auto"})
|
|
}
|
|
|
|
// =============================================================================
|
|
// Bootstrap Migration Tests
|
|
// =============================================================================
|
|
|
|
func TestBootstrapMigration(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("process bootstrap peers logic verification", func(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
peers []string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "empty peers",
|
|
peers: []string{},
|
|
expected: []string{"auto"},
|
|
},
|
|
{
|
|
name: "only default peers",
|
|
peers: []string{
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
|
|
},
|
|
expected: []string{"auto"},
|
|
},
|
|
{
|
|
name: "mixed default and custom peers",
|
|
peers: []string{
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
|
|
"/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer",
|
|
},
|
|
expected: []string{"auto", "/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer"},
|
|
},
|
|
{
|
|
name: "only custom peers",
|
|
peers: []string{
|
|
"/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer1",
|
|
"/ip4/192.168.1.2/tcp/4001/p2p/QmCustomPeer2",
|
|
},
|
|
expected: []string{
|
|
"/ip4/192.168.1.1/tcp/4001/p2p/QmCustomPeer1",
|
|
"/ip4/192.168.1.2/tcp/4001/p2p/QmCustomPeer2",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
result := processBootstrapPeers(tt.peers, "")
|
|
require.Equal(t, len(tt.expected), len(result), "Expected %d peers, got %d", len(tt.expected), len(result))
|
|
for i, expected := range tt.expected {
|
|
assert.Equal(t, expected, result[i], "Expected peer %d to be %s", i, expected)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("replaces all old default bootstrapper peers with auto entry", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := buildTestConfig(map[string]interface{}{
|
|
"Bootstrap": []string{
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
|
|
"/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
|
|
"/dnsaddr/va1.bootstrap.libp2p.io/p2p/12D3KooWKnDdG3iXw9eTFijk3EWSunZcFi54Zka4wmtqtt6rPxc8",
|
|
"/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
|
|
"/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
|
|
},
|
|
})
|
|
|
|
result := runMigrationOnJSON(t, input)
|
|
assertSliceEquals(t, result, []string{"Bootstrap"}, []string{"auto"})
|
|
})
|
|
|
|
t.Run("creates Bootstrap section with auto when missing", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := `{"Identity": {"PeerID": "QmTest"}}`
|
|
result := runMigrationOnJSON(t, input)
|
|
assertSliceEquals(t, result, []string{"Bootstrap"}, []string{"auto"})
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// DNS Migration Tests
|
|
// =============================================================================
|
|
|
|
func TestDNSMigration(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("creates DNS section with auto resolver when missing", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := `{"Identity": {"PeerID": "QmTest"}}`
|
|
result := runMigrationOnJSON(t, input)
|
|
assertMapKeyEquals(t, result, []string{"DNS", "Resolvers"}, ".", "auto")
|
|
})
|
|
|
|
t.Run("preserves all custom DNS resolvers unchanged", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := buildTestConfig(map[string]interface{}{
|
|
"DNS": map[string]interface{}{
|
|
"Resolvers": map[string]string{
|
|
".": "https://my-custom-resolver.com",
|
|
".eth": "https://eth.resolver",
|
|
},
|
|
},
|
|
})
|
|
|
|
resolvers := runMigrationAndGetDNSResolvers(t, input)
|
|
assertResolvers(t, resolvers, map[string]string{
|
|
".": "https://my-custom-resolver.com",
|
|
".eth": "https://eth.resolver",
|
|
})
|
|
})
|
|
|
|
t.Run("preserves custom dot and eth resolvers unchanged", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := buildTestConfig(map[string]interface{}{
|
|
"DNS": map[string]interface{}{
|
|
"Resolvers": map[string]string{
|
|
".": "https://cloudflare-dns.com/dns-query",
|
|
".eth": "https://example.com/dns-query",
|
|
},
|
|
},
|
|
})
|
|
|
|
resolvers := runMigrationAndGetDNSResolvers(t, input)
|
|
assertResolvers(t, resolvers, map[string]string{
|
|
".": "https://cloudflare-dns.com/dns-query",
|
|
".eth": "https://example.com/dns-query",
|
|
})
|
|
})
|
|
|
|
t.Run("replaces old default eth resolver with auto", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := buildTestConfig(map[string]interface{}{
|
|
"DNS": map[string]interface{}{
|
|
"Resolvers": map[string]string{
|
|
".": "https://cloudflare-dns.com/dns-query",
|
|
".eth": "https://dns.eth.limo/dns-query", // should be replaced
|
|
".crypto": "https://resolver.cloudflare-eth.com/dns-query", // should be replaced
|
|
".link": "https://dns.eth.link/dns-query", // should be replaced
|
|
},
|
|
},
|
|
})
|
|
|
|
resolvers := runMigrationAndGetDNSResolvers(t, input)
|
|
assertResolvers(t, resolvers, map[string]string{
|
|
".": "https://cloudflare-dns.com/dns-query", // preserved
|
|
".eth": "auto", // replaced
|
|
".crypto": "auto", // replaced
|
|
".link": "auto", // replaced
|
|
})
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// Routing Migration Tests
|
|
// =============================================================================
|
|
|
|
func TestRoutingMigration(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("creates Routing section with auto DelegatedRouters when missing", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := `{"Identity": {"PeerID": "QmTest"}}`
|
|
result := runMigrationOnJSON(t, input)
|
|
assertSliceEquals(t, result, []string{"Routing", "DelegatedRouters"}, []string{"auto"})
|
|
})
|
|
|
|
t.Run("replaces cid.contact with auto while preserving custom routers added by user", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := buildTestConfig(map[string]interface{}{
|
|
"Routing": map[string]interface{}{
|
|
"DelegatedRouters": []string{
|
|
"https://cid.contact",
|
|
"https://my-custom-router.com",
|
|
},
|
|
},
|
|
})
|
|
|
|
result := runMigrationOnJSON(t, input)
|
|
assertSliceEquals(t, result, []string{"Routing", "DelegatedRouters"}, []string{"auto", "https://my-custom-router.com"})
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// IPNS Migration Tests
|
|
// =============================================================================
|
|
|
|
func TestIpnsMigration(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("creates Ipns section with auto DelegatedPublishers when missing", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := `{"Identity": {"PeerID": "QmTest"}}`
|
|
result := runMigrationOnJSON(t, input)
|
|
assertSliceEquals(t, result, []string{"Ipns", "DelegatedPublishers"}, []string{"auto"})
|
|
})
|
|
|
|
t.Run("preserves existing custom DelegatedPublishers unchanged", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := buildTestConfig(map[string]interface{}{
|
|
"Ipns": map[string]interface{}{
|
|
"DelegatedPublishers": []string{
|
|
"https://my-publisher.com",
|
|
"https://another-publisher.com",
|
|
},
|
|
},
|
|
})
|
|
|
|
result := runMigrationOnJSON(t, input)
|
|
assertSliceEquals(t, result, []string{"Ipns", "DelegatedPublishers"}, []string{"https://my-publisher.com", "https://another-publisher.com"})
|
|
})
|
|
|
|
t.Run("adds auto DelegatedPublishers to existing Ipns section", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := buildTestConfig(map[string]interface{}{
|
|
"Ipns": map[string]interface{}{
|
|
"ResolveCacheSize": 128,
|
|
},
|
|
})
|
|
|
|
result := runMigrationOnJSON(t, input)
|
|
assertMapKeyEquals(t, result, []string{"Ipns"}, "ResolveCacheSize", float64(128))
|
|
assertSliceEquals(t, result, []string{"Ipns", "DelegatedPublishers"}, []string{"auto"})
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// AutoConf Migration Tests
|
|
// =============================================================================
|
|
|
|
func TestAutoConfMigration(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("preserves existing AutoConf fields unchanged", func(t *testing.T) {
|
|
t.Parallel()
|
|
input := buildTestConfig(map[string]interface{}{
|
|
"AutoConf": map[string]interface{}{
|
|
"URL": "https://custom.example.com/autoconf.json",
|
|
"Enabled": false,
|
|
"CustomField": "preserved",
|
|
},
|
|
})
|
|
|
|
result := runMigrationOnJSON(t, input)
|
|
assertMapKeyEquals(t, result, []string{"AutoConf"}, "URL", "https://custom.example.com/autoconf.json")
|
|
assertMapKeyEquals(t, result, []string{"AutoConf"}, "Enabled", false)
|
|
assertMapKeyEquals(t, result, []string{"AutoConf"}, "CustomField", "preserved")
|
|
})
|
|
}
|