master
go 236 lines 6.75 KB
Raw
1 package autoconf
2
3 import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "sync"
10 "testing"
11
12 "github.com/ipfs/kubo/test/cli/harness"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15 )
16
17 func TestAutoConfDelegatedRouting(t *testing.T) {
18 t.Parallel()
19
20 t.Run("delegated routing with auto router", func(t *testing.T) {
21 t.Parallel()
22 testDelegatedRoutingWithAuto(t)
23 })
24
25 t.Run("routing errors are handled properly", func(t *testing.T) {
26 t.Parallel()
27 testRoutingErrorHandling(t)
28 })
29 }
30
31 // mockRoutingServer implements a simple Delegated Routing HTTP API server
32 type mockRoutingServer struct {
33 t *testing.T
34 server *httptest.Server
35 mu sync.Mutex
36 requests []string
37 providerFunc func(cid string) []map[string]any
38 }
39
40 func newMockRoutingServer(t *testing.T) *mockRoutingServer {
41 m := &mockRoutingServer{
42 t: t,
43 requests: []string{},
44 }
45
46 // Default provider function returns mock provider records
47 m.providerFunc = func(cid string) []map[string]any {
48 return []map[string]any{
49 {
50 "Protocol": "transport-bitswap",
51 "Schema": "bitswap",
52 "ID": "12D3KooWMockProvider1",
53 "Addrs": []string{"/ip4/192.168.1.100/tcp/4001"},
54 },
55 {
56 "Protocol": "transport-bitswap",
57 "Schema": "bitswap",
58 "ID": "12D3KooWMockProvider2",
59 "Addrs": []string{"/ip4/192.168.1.101/tcp/4001"},
60 },
61 }
62 }
63
64 mux := http.NewServeMux()
65 mux.HandleFunc("/routing/v1/providers/", m.handleProviders)
66
67 m.server = httptest.NewServer(mux)
68 return m
69 }
70
71 func (m *mockRoutingServer) handleProviders(w http.ResponseWriter, r *http.Request) {
72 m.mu.Lock()
73 defer m.mu.Unlock()
74
75 // Extract CID from path
76 parts := strings.Split(r.URL.Path, "/")
77 if len(parts) < 5 {
78 http.Error(w, "invalid path", http.StatusBadRequest)
79 return
80 }
81
82 cid := parts[4]
83 m.requests = append(m.requests, cid)
84 m.t.Logf("Routing server received providers request for CID: %s", cid)
85
86 // Get provider records
87 providers := m.providerFunc(cid)
88
89 // Return NDJSON response as per IPIP-378
90 w.Header().Set("Content-Type", "application/x-ndjson")
91 encoder := json.NewEncoder(w)
92
93 for _, provider := range providers {
94 if err := encoder.Encode(provider); err != nil {
95 m.t.Logf("Failed to encode provider: %v", err)
96 return
97 }
98 }
99 }
100
101 func (m *mockRoutingServer) close() {
102 m.server.Close()
103 }
104
105 func testDelegatedRoutingWithAuto(t *testing.T) {
106 // Create mock routing server
107 routingServer := newMockRoutingServer(t)
108 defer routingServer.close()
109
110 // Create autoconf data with delegated router
111 autoConfData := fmt.Sprintf(`{
112 "AutoConfVersion": 2025072302,
113 "AutoConfSchema": 1,
114 "AutoConfTTL": 86400,
115 "SystemRegistry": {
116 "AminoDHT": {
117 "Description": "Test AminoDHT system",
118 "NativeConfig": {
119 "Bootstrap": []
120 }
121 }
122 },
123 "DNSResolvers": {},
124 "DelegatedEndpoints": {
125 "%s": {
126 "Systems": ["AminoDHT", "IPNI"],
127 "Read": ["/routing/v1/providers", "/routing/v1/peers", "/routing/v1/ipns"],
128 "Write": []
129 }
130 }
131 }`, routingServer.server.URL)
132
133 // Create autoconf server
134 autoConfServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
135 w.Header().Set("Content-Type", "application/json")
136 _, _ = w.Write([]byte(autoConfData))
137 }))
138 defer autoConfServer.Close()
139
140 // Create IPFS node with auto delegated router
141 node := harness.NewT(t).NewNode().Init("--profile=test")
142 node.SetIPFSConfig("AutoConf.URL", autoConfServer.URL)
143 node.SetIPFSConfig("AutoConf.Enabled", true)
144 node.SetIPFSConfig("Routing.DelegatedRouters", []string{"auto"})
145
146 // Test that daemon starts successfully with auto routing configuration
147 // The actual routing functionality requires online mode, but we can test
148 // that the configuration is expanded and daemon starts properly
149 node.StartDaemon("--offline")
150 defer node.StopDaemon()
151
152 // Verify config still shows "auto" (this tests that auto values are preserved in user-facing config)
153 result := node.RunIPFS("config", "Routing.DelegatedRouters")
154 require.Equal(t, 0, result.ExitCode())
155
156 var routers []string
157 err := json.Unmarshal([]byte(result.Stdout.String()), &routers)
158 require.NoError(t, err)
159 assert.Equal(t, []string{"auto"}, routers, "Delegated routers config should show 'auto'")
160
161 // Test that daemon is running and accepting commands
162 result = node.RunIPFS("version")
163 require.Equal(t, 0, result.ExitCode(), "Daemon should be running and accepting commands")
164
165 // Test that autoconf server was contacted (indicating successful resolution)
166 // We can't test actual routing in offline mode, but we can verify that
167 // the AutoConf system expanded the "auto" placeholder successfully
168 // by checking that the daemon started without errors
169 t.Log("AutoConf successfully expanded delegated router configuration and daemon started")
170 }
171
172 func testRoutingErrorHandling(t *testing.T) {
173 // Create routing server that returns no providers
174 routingServer := newMockRoutingServer(t)
175 defer routingServer.close()
176
177 // Configure to return no providers (empty response)
178 routingServer.providerFunc = func(cid string) []map[string]any {
179 return []map[string]any{}
180 }
181
182 // Create autoconf data
183 autoConfData := fmt.Sprintf(`{
184 "AutoConfVersion": 2025072302,
185 "AutoConfSchema": 1,
186 "AutoConfTTL": 86400,
187 "SystemRegistry": {
188 "AminoDHT": {
189 "Description": "Test AminoDHT system",
190 "NativeConfig": {
191 "Bootstrap": []
192 }
193 }
194 },
195 "DNSResolvers": {},
196 "DelegatedEndpoints": {
197 "%s": {
198 "Systems": ["AminoDHT", "IPNI"],
199 "Read": ["/routing/v1/providers", "/routing/v1/peers", "/routing/v1/ipns"],
200 "Write": []
201 }
202 }
203 }`, routingServer.server.URL)
204
205 // Create autoconf server
206 autoConfServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
207 w.Header().Set("Content-Type", "application/json")
208 _, _ = w.Write([]byte(autoConfData))
209 }))
210 defer autoConfServer.Close()
211
212 // Create IPFS node
213 node := harness.NewT(t).NewNode().Init("--profile=test")
214 node.SetIPFSConfig("AutoConf.URL", autoConfServer.URL)
215 node.SetIPFSConfig("AutoConf.Enabled", true)
216 node.SetIPFSConfig("Routing.DelegatedRouters", []string{"auto"})
217
218 // Test that daemon starts successfully even when no providers are available
219 node.StartDaemon("--offline")
220 defer node.StopDaemon()
221
222 // Verify config shows "auto"
223 result := node.RunIPFS("config", "Routing.DelegatedRouters")
224 require.Equal(t, 0, result.ExitCode())
225
226 var routers []string
227 err := json.Unmarshal([]byte(result.Stdout.String()), &routers)
228 require.NoError(t, err)
229 assert.Equal(t, []string{"auto"}, routers, "Delegated routers config should show 'auto'")
230
231 // Test that daemon is running and accepting commands
232 result = node.RunIPFS("version")
233 require.Equal(t, 0, result.ExitCode(), "Daemon should be running even with empty routing config")
234
235 t.Log("AutoConf successfully handled routing configuration with empty providers")
236 }