master
go 316 lines 10.7 KB
Raw
1 package libp2p
2
3 import (
4 "context"
5 "testing"
6
7 "github.com/ipfs/boxo/autoconf"
8 config "github.com/ipfs/kubo/config"
9 "github.com/libp2p/go-libp2p/core/connmgr"
10 "github.com/libp2p/go-libp2p/core/event"
11 "github.com/libp2p/go-libp2p/core/network"
12 "github.com/libp2p/go-libp2p/core/peer"
13 "github.com/libp2p/go-libp2p/core/peerstore"
14 "github.com/libp2p/go-libp2p/core/protocol"
15 ma "github.com/multiformats/go-multiaddr"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18 )
19
20 func TestDetermineCapabilities(t *testing.T) {
21 tests := []struct {
22 name string
23 endpoint EndpointSource
24 expectedBaseURL string
25 expectedCapabilities autoconf.EndpointCapabilities
26 expectError bool
27 }{
28 {
29 name: "URL with no path should have all Read capabilities",
30 endpoint: EndpointSource{
31 URL: "https://example.com",
32 SupportsRead: true,
33 SupportsWrite: false,
34 },
35 expectedBaseURL: "https://example.com",
36 expectedCapabilities: autoconf.EndpointCapabilities{
37 Providers: true,
38 Peers: true,
39 IPNSGet: true,
40 IPNSPut: false,
41 },
42 expectError: false,
43 },
44 {
45 name: "URL with trailing slash should have all Read capabilities",
46 endpoint: EndpointSource{
47 URL: "https://example.com/",
48 SupportsRead: true,
49 SupportsWrite: false,
50 },
51 expectedBaseURL: "https://example.com",
52 expectedCapabilities: autoconf.EndpointCapabilities{
53 Providers: true,
54 Peers: true,
55 IPNSGet: true,
56 IPNSPut: false,
57 },
58 expectError: false,
59 },
60 {
61 name: "URL with IPNS path should have only IPNS capabilities",
62 endpoint: EndpointSource{
63 URL: "https://example.com/routing/v1/ipns",
64 SupportsRead: true,
65 SupportsWrite: true,
66 },
67 expectedBaseURL: "https://example.com",
68 expectedCapabilities: autoconf.EndpointCapabilities{
69 Providers: false,
70 Peers: false,
71 IPNSGet: true,
72 IPNSPut: true,
73 },
74 expectError: false,
75 },
76 {
77 name: "URL with providers path should have only Providers capability",
78 endpoint: EndpointSource{
79 URL: "https://example.com/routing/v1/providers",
80 SupportsRead: true,
81 SupportsWrite: false,
82 },
83 expectedBaseURL: "https://example.com",
84 expectedCapabilities: autoconf.EndpointCapabilities{
85 Providers: true,
86 Peers: false,
87 IPNSGet: false,
88 IPNSPut: false,
89 },
90 expectError: false,
91 },
92 {
93 name: "URL with peers path should have only Peers capability",
94 endpoint: EndpointSource{
95 URL: "https://example.com/routing/v1/peers",
96 SupportsRead: true,
97 SupportsWrite: false,
98 },
99 expectedBaseURL: "https://example.com",
100 expectedCapabilities: autoconf.EndpointCapabilities{
101 Providers: false,
102 Peers: true,
103 IPNSGet: false,
104 IPNSPut: false,
105 },
106 expectError: false,
107 },
108 {
109 name: "URL with Write support only should enable IPNSPut for no-path endpoint",
110 endpoint: EndpointSource{
111 URL: "https://example.com",
112 SupportsRead: false,
113 SupportsWrite: true,
114 },
115 expectedBaseURL: "https://example.com",
116 expectedCapabilities: autoconf.EndpointCapabilities{
117 Providers: false,
118 Peers: false,
119 IPNSGet: false,
120 IPNSPut: true,
121 },
122 expectError: false,
123 },
124 }
125
126 for _, tt := range tests {
127 t.Run(tt.name, func(t *testing.T) {
128 baseURL, capabilities, err := determineCapabilities(tt.endpoint)
129
130 if tt.expectError {
131 assert.Error(t, err)
132 return
133 }
134
135 require.NoError(t, err)
136 assert.Equal(t, tt.expectedBaseURL, baseURL)
137 assert.Equal(t, tt.expectedCapabilities, capabilities)
138 })
139 }
140 }
141
142 func TestEndpointCapabilitiesReadWriteLogic(t *testing.T) {
143 t.Run("Read endpoint with no path should enable read capabilities", func(t *testing.T) {
144 endpoint := EndpointSource{
145 URL: "https://example.com",
146 SupportsRead: true,
147 SupportsWrite: false,
148 }
149 _, capabilities, err := determineCapabilities(endpoint)
150 require.NoError(t, err)
151
152 // Read endpoint with no path should enable all read capabilities
153 assert.True(t, capabilities.Providers)
154 assert.True(t, capabilities.Peers)
155 assert.True(t, capabilities.IPNSGet)
156 assert.False(t, capabilities.IPNSPut) // Write capability should be false
157 })
158
159 t.Run("Write endpoint with no path should enable write capabilities", func(t *testing.T) {
160 endpoint := EndpointSource{
161 URL: "https://example.com",
162 SupportsRead: false,
163 SupportsWrite: true,
164 }
165 _, capabilities, err := determineCapabilities(endpoint)
166 require.NoError(t, err)
167
168 // Write endpoint with no path should only enable IPNS write capability
169 assert.False(t, capabilities.Providers)
170 assert.False(t, capabilities.Peers)
171 assert.False(t, capabilities.IPNSGet)
172 assert.True(t, capabilities.IPNSPut) // Only write capability should be true
173 })
174
175 t.Run("Specific path should only enable matching capabilities", func(t *testing.T) {
176 endpoint := EndpointSource{
177 URL: "https://example.com/routing/v1/ipns",
178 SupportsRead: true,
179 SupportsWrite: true,
180 }
181 _, capabilities, err := determineCapabilities(endpoint)
182 require.NoError(t, err)
183
184 // Specific IPNS path should only enable IPNS capabilities based on source
185 assert.False(t, capabilities.Providers)
186 assert.False(t, capabilities.Peers)
187 assert.True(t, capabilities.IPNSGet) // Read capability enabled
188 assert.True(t, capabilities.IPNSPut) // Write capability enabled
189 })
190
191 t.Run("Unsupported paths should result in empty capabilities", func(t *testing.T) {
192 endpoint := EndpointSource{
193 URL: "https://example.com/routing/v1/unsupported",
194 SupportsRead: true,
195 SupportsWrite: false,
196 }
197 _, capabilities, err := determineCapabilities(endpoint)
198 require.NoError(t, err)
199
200 // Unsupported paths should result in no capabilities
201 assert.False(t, capabilities.Providers)
202 assert.False(t, capabilities.Peers)
203 assert.False(t, capabilities.IPNSGet)
204 assert.False(t, capabilities.IPNSPut)
205 })
206 }
207
208 // stubHost is a minimal host.Host stub for testing httpRouterAddrFunc.
209 // reachable mocks ConfirmedAddrs (AutoNAT V2 result); hostAddrs mocks
210 // Addrs(), which in a real host returns wildcard-resolved interface
211 // addresses after the AddrsFactory filters (NoAnnounce/AddrFilters).
212 type stubHost struct {
213 reachable []ma.Multiaddr
214 hostAddrs []ma.Multiaddr
215 }
216
217 func (h *stubHost) ConfirmedAddrs() (reachable, unreachable, unknown []ma.Multiaddr) {
218 return h.reachable, nil, nil
219 }
220
221 func (h *stubHost) ID() peer.ID { panic("unused") }
222 func (h *stubHost) Addrs() []ma.Multiaddr { return h.hostAddrs }
223 func (h *stubHost) Peerstore() peerstore.Peerstore { panic("unused") }
224 func (h *stubHost) Network() network.Network { panic("unused") }
225 func (h *stubHost) Mux() protocol.Switch { panic("unused") }
226 func (h *stubHost) Connect(context.Context, peer.AddrInfo) error { panic("unused") }
227 func (h *stubHost) SetStreamHandler(protocol.ID, network.StreamHandler) { panic("unused") }
228 func (h *stubHost) SetStreamHandlerMatch(protocol.ID, func(protocol.ID) bool, network.StreamHandler) {
229 panic("unused")
230 }
231 func (h *stubHost) RemoveStreamHandler(protocol.ID) { panic("unused") }
232 func (h *stubHost) NewStream(context.Context, peer.ID, ...protocol.ID) (network.Stream, error) {
233 panic("unused")
234 }
235 func (h *stubHost) Close() error { panic("unused") }
236 func (h *stubHost) ConnManager() connmgr.ConnManager { panic("unused") }
237 func (h *stubHost) EventBus() event.Bus { panic("unused") }
238
239 func TestHttpRouterAddrFunc(t *testing.T) {
240 // hostAddrs simulates what host.Addrs() returns in a running daemon:
241 // wildcard Swarm binds resolved to concrete interfaces, with the
242 // libp2p AddrsFactory (NoAnnounce/AddrFilters) already applied.
243 resolvedAddrs := []string{
244 "/ip4/192.168.1.10/tcp/4001",
245 "/ip4/192.168.1.10/udp/4001/quic-v1",
246 }
247
248 tests := []struct {
249 name string
250 reachable []string // autonat confirmed addrs (nil = none)
251 hostAddrs []string // host.Addrs() output (nil = none)
252 cfg config.Addresses
253 want []string
254 }{
255 {
256 name: "prefers autonat confirmed reachable addrs over host.Addrs fallback",
257 reachable: []string{"/ip4/1.2.3.4/tcp/4001", "/ip4/1.2.3.4/udp/4001/quic-v1"},
258 hostAddrs: resolvedAddrs,
259 cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}},
260 want: []string{"/ip4/1.2.3.4/tcp/4001", "/ip4/1.2.3.4/udp/4001/quic-v1"},
261 },
262 {
263 name: "falls back to host.Addrs when autonat has no confirmed addrs",
264 hostAddrs: resolvedAddrs,
265 cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}},
266 want: resolvedAddrs,
267 },
268 {
269 name: "Announce overrides autonat and host.Addrs",
270 reachable: []string{"/ip4/1.2.3.4/tcp/4001"},
271 hostAddrs: resolvedAddrs,
272 cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, Announce: []string{"/ip4/5.6.7.8/tcp/4001"}},
273 want: []string{"/ip4/5.6.7.8/tcp/4001"},
274 },
275 {
276 name: "AppendAnnounce added to autonat addrs",
277 reachable: []string{"/ip4/1.2.3.4/tcp/4001"},
278 hostAddrs: resolvedAddrs,
279 cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, AppendAnnounce: []string{"/ip4/10.0.0.1/tcp/4001"}},
280 want: []string{"/ip4/1.2.3.4/tcp/4001", "/ip4/10.0.0.1/tcp/4001"},
281 },
282 {
283 name: "AppendAnnounce added to host.Addrs fallback",
284 hostAddrs: resolvedAddrs,
285 cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, AppendAnnounce: []string{"/ip4/10.0.0.1/tcp/4001"}},
286 want: append(append([]string{}, resolvedAddrs...), "/ip4/10.0.0.1/tcp/4001"),
287 },
288 {
289 // NoAnnounce (including server profile CIDR ranges) is applied by the
290 // libp2p AddrsFactory before host.Addrs() returns, so httpRouterAddrFunc
291 // itself performs no filtering on the fallback.
292 name: "NoAnnounce filtering happens upstream in host.Addrs",
293 hostAddrs: []string{"/ip4/192.168.1.10/tcp/4001"}, // already filtered by addrFactory
294 cfg: config.Addresses{
295 Swarm: []string{"/ip4/0.0.0.0/tcp/4001"},
296 NoAnnounce: []string{"/ip4/127.0.0.0/ipcidr/8"},
297 },
298 want: []string{"/ip4/192.168.1.10/tcp/4001"},
299 },
300 {
301 name: "AppendAnnounce added to Announce",
302 cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, Announce: []string{"/ip4/5.6.7.8/tcp/4001"}, AppendAnnounce: []string{"/ip4/10.0.0.1/tcp/4001"}},
303 want: []string{"/ip4/5.6.7.8/tcp/4001", "/ip4/10.0.0.1/tcp/4001"},
304 },
305 }
306 for _, tt := range tests {
307 t.Run(tt.name, func(t *testing.T) {
308 h := &stubHost{
309 reachable: parseMultiaddrs(tt.reachable),
310 hostAddrs: parseMultiaddrs(tt.hostAddrs),
311 }
312 fn := httpRouterAddrFunc(h, tt.cfg)
313 assert.Equal(t, parseMultiaddrs(tt.want), fn())
314 })
315 }
316 }