master
go 284 lines 11.8 KB
Raw
1 package autoconf
2
3 import (
4 "encoding/json"
5 "net/http"
6 "net/http/httptest"
7 "os"
8 "slices"
9 "testing"
10 "time"
11
12 "github.com/ipfs/boxo/autoconf"
13 "github.com/ipfs/kubo/test/cli/harness"
14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/require"
16 )
17
18 func TestExpandAutoFallbacks(t *testing.T) {
19 t.Parallel()
20
21 t.Run("expand-auto with unreachable server shows fallbacks", func(t *testing.T) {
22 t.Parallel()
23 testExpandAutoWithUnreachableServer(t)
24 })
25
26 t.Run("expand-auto with disabled autoconf shows error", func(t *testing.T) {
27 t.Parallel()
28 testExpandAutoWithDisabledAutoConf(t)
29 })
30
31 t.Run("expand-auto with malformed response shows fallbacks", func(t *testing.T) {
32 t.Parallel()
33 testExpandAutoWithMalformedResponse(t)
34 })
35
36 t.Run("expand-auto preserves static values in mixed config", func(t *testing.T) {
37 t.Parallel()
38 testExpandAutoMixedConfigPreservesStatic(t)
39 })
40
41 t.Run("daemon gracefully handles malformed autoconf and uses fallbacks", func(t *testing.T) {
42 t.Parallel()
43 testDaemonWithMalformedAutoConf(t)
44 })
45 }
46
47 func testExpandAutoWithUnreachableServer(t *testing.T) {
48 // Create IPFS node with unreachable AutoConf server
49 node := harness.NewT(t).NewNode().Init("--profile=test")
50 node.SetIPFSConfig("AutoConf.URL", "http://127.0.0.1:99999/nonexistent") // Unreachable
51 node.SetIPFSConfig("AutoConf.Enabled", true)
52 node.SetIPFSConfig("Bootstrap", []string{"auto"})
53 node.SetIPFSConfig("DNS.Resolvers", map[string]string{"foo.": "auto"})
54
55 // Test that --expand-auto falls back to defaults when server is unreachable
56 result := node.RunIPFS("config", "Bootstrap", "--expand-auto")
57 require.Equal(t, 0, result.ExitCode(), "config Bootstrap --expand-auto should succeed even with unreachable server")
58
59 var bootstrap []string
60 err := json.Unmarshal([]byte(result.Stdout.String()), &bootstrap)
61 require.NoError(t, err)
62
63 // Should contain fallback bootstrap peers (not "auto" and not empty)
64 assert.NotContains(t, bootstrap, "auto", "Fallback bootstrap should not contain 'auto'")
65 assert.Greater(t, len(bootstrap), 0, "Fallback bootstrap should not be empty")
66
67 // Should contain known default bootstrap peers
68 foundDefaultPeer := false
69 for _, peer := range bootstrap {
70 if peer != "" && peer != "auto" {
71 foundDefaultPeer = true
72 t.Logf("Found fallback bootstrap peer: %s", peer)
73 break
74 }
75 }
76 assert.True(t, foundDefaultPeer, "Should contain at least one fallback bootstrap peer")
77
78 // Test DNS resolvers fallback
79 result = node.RunIPFS("config", "DNS.Resolvers", "--expand-auto")
80 require.Equal(t, 0, result.ExitCode(), "config DNS.Resolvers --expand-auto should succeed with unreachable server")
81
82 var resolvers map[string]string
83 err = json.Unmarshal([]byte(result.Stdout.String()), &resolvers)
84 require.NoError(t, err)
85
86 // When autoconf server is unreachable, DNS resolvers should fall back to defaults
87 // The "foo." resolver should not exist in fallbacks (only "eth." has fallback)
88 fooResolver, fooExists := resolvers["foo."]
89
90 if !fooExists {
91 t.Log("DNS resolver for 'foo.' has no fallback - correct behavior (only eth. has fallbacks)")
92 } else {
93 assert.NotEqual(t, "auto", fooResolver, "DNS resolver should not be 'auto' after expansion")
94 t.Logf("Unexpected DNS resolver for foo.: %s", fooResolver)
95 }
96 }
97
98 func testExpandAutoWithDisabledAutoConf(t *testing.T) {
99 // Create IPFS node with AutoConf disabled
100 node := harness.NewT(t).NewNode().Init("--profile=test")
101 node.SetIPFSConfig("AutoConf.Enabled", false)
102 node.SetIPFSConfig("Bootstrap", []string{"auto"})
103
104 // Test that --expand-auto with disabled AutoConf returns appropriate error or fallback
105 result := node.RunIPFS("config", "Bootstrap", "--expand-auto")
106
107 // When AutoConf is disabled, expand-auto should show empty results
108 // since "auto" values are not expanded when AutoConf.Enabled=false
109 var bootstrap []string
110 err := json.Unmarshal([]byte(result.Stdout.String()), &bootstrap)
111 require.NoError(t, err)
112
113 // With AutoConf disabled, "auto" values are not expanded so we get empty result
114 assert.NotContains(t, bootstrap, "auto", "Should not contain 'auto' after expansion")
115 assert.Equal(t, 0, len(bootstrap), "Should be empty when AutoConf disabled (auto values not expanded)")
116 t.Log("Bootstrap is empty when AutoConf disabled - correct behavior")
117 }
118
119 func testExpandAutoWithMalformedResponse(t *testing.T) {
120 // Create server that returns malformed JSON
121 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
122 w.Header().Set("Content-Type", "application/json")
123 _, _ = w.Write([]byte(`{"invalid": "json", "Bootstrap": [incomplete`)) // Malformed JSON
124 }))
125 defer server.Close()
126
127 // Create IPFS node with malformed autoconf server
128 node := harness.NewT(t).NewNode().Init("--profile=test")
129 node.SetIPFSConfig("AutoConf.URL", server.URL)
130 node.SetIPFSConfig("AutoConf.Enabled", true)
131 node.SetIPFSConfig("Bootstrap", []string{"auto"})
132
133 // Test that --expand-auto handles malformed response gracefully
134 result := node.RunIPFS("config", "Bootstrap", "--expand-auto")
135 require.Equal(t, 0, result.ExitCode(), "config Bootstrap --expand-auto should succeed even with malformed response")
136
137 var bootstrap []string
138 err := json.Unmarshal([]byte(result.Stdout.String()), &bootstrap)
139 require.NoError(t, err)
140
141 // Should fall back to defaults, not contain "auto"
142 assert.NotContains(t, bootstrap, "auto", "Should not contain 'auto' after fallback")
143 assert.Greater(t, len(bootstrap), 0, "Should contain fallback peers after malformed response")
144 t.Logf("Bootstrap after malformed response: %v", bootstrap)
145 }
146
147 func testExpandAutoMixedConfigPreservesStatic(t *testing.T) {
148 // Load valid test autoconf data
149 autoConfData := loadTestDataForFallback(t, "valid_autoconf.json")
150
151 // Create HTTP server that serves autoconf.json
152 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
153 w.Header().Set("Content-Type", "application/json")
154 _, _ = w.Write(autoConfData)
155 }))
156 defer server.Close()
157
158 // Create IPFS node with mixed auto and static values
159 node := harness.NewT(t).NewNode().Init("--profile=test")
160 node.SetIPFSConfig("AutoConf.URL", server.URL)
161 node.SetIPFSConfig("AutoConf.Enabled", true)
162
163 // Set mixed configuration: static + auto + static
164 node.SetIPFSConfig("Bootstrap", []string{
165 "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWTest",
166 "auto",
167 "/ip4/127.0.0.2/tcp/4001/p2p/12D3KooWTest2",
168 })
169
170 // Test that --expand-auto only expands "auto" values, preserves static ones
171 result := node.RunIPFS("config", "Bootstrap", "--expand-auto")
172 require.Equal(t, 0, result.ExitCode(), "config Bootstrap --expand-auto should succeed")
173
174 var bootstrap []string
175 err := json.Unmarshal([]byte(result.Stdout.String()), &bootstrap)
176 require.NoError(t, err)
177
178 // Should not contain literal "auto" anymore
179 assert.NotContains(t, bootstrap, "auto", "Expanded config should not contain literal 'auto'")
180
181 // Should preserve static values at original positions
182 assert.Contains(t, bootstrap, "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWTest", "Should preserve first static peer")
183 assert.Contains(t, bootstrap, "/ip4/127.0.0.2/tcp/4001/p2p/12D3KooWTest2", "Should preserve third static peer")
184
185 // Should have more entries than just the static ones (auto got expanded)
186 assert.Greater(t, len(bootstrap), 2, "Should have more than just the 2 static peers")
187
188 t.Logf("Mixed config expansion result: %v", bootstrap)
189
190 // Verify order is preserved: static, expanded auto values, static
191 assert.Equal(t, "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWTest", bootstrap[0], "First peer should be preserved")
192 lastIndex := len(bootstrap) - 1
193 assert.Equal(t, "/ip4/127.0.0.2/tcp/4001/p2p/12D3KooWTest2", bootstrap[lastIndex], "Last peer should be preserved")
194 }
195
196 func testDaemonWithMalformedAutoConf(t *testing.T) {
197 // Test scenario: Daemon starts with AutoConf.URL pointing to server that returns malformed JSON
198 // This tests that daemon gracefully handles malformed responses and falls back to hardcoded defaults
199
200 // Create server that returns malformed JSON to simulate broken autoconf service
201 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
202 w.Header().Set("Content-Type", "application/json")
203 // Return malformed JSON that cannot be parsed
204 _, _ = w.Write([]byte(`{"Bootstrap": ["incomplete array", "missing closing bracket"`))
205 }))
206 defer server.Close()
207
208 // Create IPFS node with autoconf pointing to malformed server
209 node := harness.NewT(t).NewNode().Init("--profile=test")
210 node.SetIPFSConfig("AutoConf.URL", server.URL)
211 node.SetIPFSConfig("AutoConf.Enabled", true)
212 node.SetIPFSConfig("Bootstrap", []string{"auto"})
213 node.SetIPFSConfig("DNS.Resolvers", map[string]string{"foo.": "auto"})
214
215 // Start daemon - this will attempt to fetch autoconf from malformed server
216 t.Log("Starting daemon with malformed autoconf server...")
217 daemon := node.StartDaemon()
218 defer daemon.StopDaemon()
219
220 // Wait for daemon to attempt autoconf fetch and handle the error gracefully
221 time.Sleep(6 * time.Second) // defaultTimeout is 5s, add 1s buffer
222 t.Log("Daemon should have attempted autoconf fetch and fallen back to defaults")
223
224 // Test that daemon is still running and CLI commands work with fallback values
225 result := node.RunIPFS("config", "Bootstrap", "--expand-auto")
226 require.Equal(t, 0, result.ExitCode(), "config Bootstrap --expand-auto should succeed with daemon running")
227
228 var bootstrap []string
229 err := json.Unmarshal([]byte(result.Stdout.String()), &bootstrap)
230 require.NoError(t, err)
231
232 // Should fall back to hardcoded defaults from GetMainnetFallbackConfig()
233 // NOTE: These values may change if autoconf library updates GetMainnetFallbackConfig()
234 assert.NotContains(t, bootstrap, "auto", "Should not contain 'auto' after fallback")
235 assert.Greater(t, len(bootstrap), 0, "Should contain fallback bootstrap peers")
236
237 // Verify we got actual fallback bootstrap peers from GetMainnetFallbackConfig() AminoDHT NativeConfig
238 fallbackConfig := autoconf.GetMainnetFallbackConfig()
239 aminoDHTSystem := fallbackConfig.SystemRegistry["AminoDHT"]
240 expectedBootstrapPeers := aminoDHTSystem.NativeConfig.Bootstrap
241
242 foundFallbackPeers := 0
243 for _, expectedPeer := range expectedBootstrapPeers {
244 if slices.Contains(bootstrap, expectedPeer) {
245 foundFallbackPeers++
246 }
247 }
248 assert.Greater(t, foundFallbackPeers, 0, "Should contain bootstrap peers from GetMainnetFallbackConfig() AminoDHT NativeConfig")
249 assert.Equal(t, len(expectedBootstrapPeers), foundFallbackPeers, "Should contain all bootstrap peers from GetMainnetFallbackConfig() AminoDHT NativeConfig")
250
251 t.Logf("Daemon fallback bootstrap peers after malformed response: %v", bootstrap)
252
253 // Test DNS resolvers also fall back correctly
254 result = node.RunIPFS("config", "DNS.Resolvers", "--expand-auto")
255 require.Equal(t, 0, result.ExitCode(), "config DNS.Resolvers --expand-auto should succeed with daemon running")
256
257 var resolvers map[string]string
258 err = json.Unmarshal([]byte(result.Stdout.String()), &resolvers)
259 require.NoError(t, err)
260
261 // Should not contain "auto" and should have fallback DNS resolvers
262 assert.NotEqual(t, "auto", resolvers["foo."], "DNS resolver should not be 'auto' after fallback")
263 if resolvers["foo."] != "" {
264 // If resolver is populated, it should be a valid URL from fallbacks
265 assert.Contains(t, resolvers["foo."], "https://", "Fallback DNS resolver should be HTTPS URL")
266 }
267
268 t.Logf("Daemon fallback DNS resolvers after malformed response: %v", resolvers)
269
270 // Verify daemon is still healthy and responsive
271 versionResult := node.RunIPFS("version")
272 require.Equal(t, 0, versionResult.ExitCode(), "daemon should remain healthy after handling malformed autoconf")
273 t.Log("Daemon remains healthy after gracefully handling malformed autoconf response")
274 }
275
276 // Helper function to load test data files for fallback tests
277 func loadTestDataForFallback(t *testing.T, filename string) []byte {
278 t.Helper()
279
280 data, err := os.ReadFile("testdata/" + filename)
281 require.NoError(t, err, "Failed to read test data file: %s", filename)
282
283 return data
284 }