@cryptotaxi247 / kubo / commits / 02d4a0100

test: port gateway sharness tests to Go tests

Gus Eggert committed Dec 14, 2022 at 11:10 UTC 02d4a0100f5b50d556cbbf3a95c1b949b3e8a2ef
9 files changed +727 -378
test/cli/gateway_test.go new
+492
@@ -0,0 +1,492 @@
1 +package cli
2 +
3 +import (
4 + "context"
5 + "encoding/json"
6 + "fmt"
7 + "net/http"
8 + "os"
9 + "path/filepath"
10 + "regexp"
11 + "testing"
12 +
13 + "github.com/ipfs/kubo/config"
14 + "github.com/ipfs/kubo/test/cli/harness"
15 + . "github.com/ipfs/kubo/test/cli/testutils"
16 + "github.com/multiformats/go-multiaddr"
17 + manet "github.com/multiformats/go-multiaddr/net"
18 + "github.com/stretchr/testify/assert"
19 + "github.com/stretchr/testify/require"
20 +)
21 +
22 +func TestGateway(t *testing.T) {
23 + t.Parallel()
24 + h := harness.NewT(t)
25 + node := h.NewNode().Init().StartDaemon("--offline")
26 + cid := node.IPFSAddStr("Hello Worlds!")
27 +
28 + client := node.GatewayClient()
29 + client.TemplateData = map[string]string{
30 + "CID": cid,
31 + "PeerID": node.PeerID().String(),
32 + }
33 +
34 + t.Run("GET IPFS path succeeds", func(t *testing.T) {
35 + t.Parallel()
36 + resp := client.Get("/ipfs/{{.CID}}")
37 + assert.Equal(t, 200, resp.StatusCode)
38 + })
39 +
40 + t.Run("GET IPFS path with explicit ?filename succeeds with proper header", func(t *testing.T) {
41 + t.Parallel()
42 + resp := client.Get("/ipfs/{{.CID}}?filename=testтест.pdf")
43 + assert.Equal(t, 200, resp.StatusCode)
44 + assert.Equal(t,
45 + `inline; filename="test____.pdf"; filename*=UTF-8''test%D1%82%D0%B5%D1%81%D1%82.pdf`,
46 + resp.Headers.Get("Content-Disposition"),
47 + )
48 + })
49 +
50 + t.Run("GET IPFS path with explicit ?filename and &download=true succeeds with proper header", func(t *testing.T) {
51 + t.Parallel()
52 + resp := client.Get("/ipfs/{{.CID}}?filename=testтест.mp4&download=true")
53 + assert.Equal(t, 200, resp.StatusCode)
54 + assert.Equal(t,
55 + `attachment; filename="test____.mp4"; filename*=UTF-8''test%D1%82%D0%B5%D1%81%D1%82.mp4`,
56 + resp.Headers.Get("Content-Disposition"),
57 + )
58 + })
59 +
60 + // https://github.com/ipfs/go-ipfs/issues/4025#issuecomment-342250616
61 + t.Run("GET for Server Worker registration outside of an IPFS content root errors", func(t *testing.T) {
62 + t.Parallel()
63 + resp := client.Get("/ipfs/{{.CID}}?filename=sw.js", client.WithHeader("Service-Worker", "script"))
64 + assert.Equal(t, 400, resp.StatusCode)
65 + assert.Contains(t, resp.Body, "navigator.serviceWorker: registration is not allowed for this scope")
66 + })
67 +
68 + t.Run("GET IPFS directory path succeeds", func(t *testing.T) {
69 + t.Parallel()
70 + client := node.GatewayClient().DisableRedirects()
71 +
72 + pageContents := "hello i am a webpage"
73 + fileContents := "12345"
74 + h.WriteFile("dir/test", fileContents)
75 + h.WriteFile("dir/dirwithindex/index.html", pageContents)
76 + cids := node.IPFS("add", "-r", "-q", filepath.Join(h.Dir, "dir")).Stdout.Lines()
77 +
78 + rootCID := cids[len(cids)-1]
79 + client.TemplateData = map[string]string{
80 + "IndexFileCID": cids[0],
81 + "TestFileCID": cids[1],
82 + "RootCID": rootCID,
83 + }
84 +
85 + t.Run("GET IPFS the index file CID", func(t *testing.T) {
86 + t.Parallel()
87 + resp := client.Get("/ipfs/{{.IndexFileCID}}")
88 + assert.Equal(t, 200, resp.StatusCode)
89 + assert.Equal(t, pageContents, resp.Body)
90 + })
91 +
92 + t.Run("GET IPFS the test file CID", func(t *testing.T) {
93 + t.Parallel()
94 + resp := client.Get("/ipfs/{{.TestFileCID}}")
95 + assert.Equal(t, 200, resp.StatusCode)
96 + assert.Equal(t, fileContents, resp.Body)
97 + })
98 +
99 + t.Run("GET IPFS directory with index.html returns redirect to add trailing slash", func(t *testing.T) {
100 + t.Parallel()
101 + resp := client.Head("/ipfs/{{.RootCID}}/dirwithindex?query=to-remember")
102 + assert.Equal(t, 301, resp.StatusCode)
103 + assert.Equal(t,
104 + fmt.Sprintf("/ipfs/%s/dirwithindex/?query=to-remember", rootCID),
105 + resp.Headers.Get("Location"),
106 + )
107 + })
108 +
109 + // This enables go get to parse go-import meta tags from index.html files stored in IPFS
110 + // https://github.com/ipfs/kubo/pull/3963
111 + t.Run("GET IPFS directory with index.html and no trailing slash returns expected output when go-get is passed", func(t *testing.T) {
112 + t.Parallel()
113 + resp := client.Get("/ipfs/{{.RootCID}}/dirwithindex?go-get=1")
114 + assert.Equal(t, pageContents, resp.Body)
115 + })
116 +
117 + t.Run("GET IPFS directory with index.html and trailing slash returns expected output", func(t *testing.T) {
118 + t.Parallel()
119 + resp := client.Get("/ipfs/{{.RootCID}}/dirwithindex/?query=to-remember")
120 + assert.Equal(t, pageContents, resp.Body)
121 + })
122 +
123 + t.Run("GET IPFS nonexistent file returns 404 (Not Found)", func(t *testing.T) {
124 + t.Parallel()
125 + resp := client.Get("/ipfs/{{.RootCID}}/pleaseDontAddMe")
126 + assert.Equal(t, 404, resp.StatusCode)
127 + })
128 +
129 + t.Run("GET IPFS invalid CID returns 400 (Bad Request)", func(t *testing.T) {
130 + t.Parallel()
131 + resp := client.Get("/ipfs/QmInvalid/pleaseDontAddMe")
132 + assert.Equal(t, 400, resp.StatusCode)
133 + })
134 +
135 + t.Run("GET IPFS inlined zero-length data object returns ok code (200)", func(t *testing.T) {
136 + t.Parallel()
137 + resp := client.Get("/ipfs/bafkqaaa")
138 + assert.Equal(t, 200, resp.StatusCode)
139 + assert.Equal(t, "0", resp.Resp.Header.Get("Content-Length"))
140 + assert.Equal(t, "", resp.Body)
141 + })
142 +
143 + t.Run("GET IPFS inlined zero-length data object with byte range returns ok code (200)", func(t *testing.T) {
144 + t.Parallel()
145 + resp := client.Get("/ipfs/bafkqaaa", client.WithHeader("Range", "bytes=0-1048575"))
146 + assert.Equal(t, 200, resp.StatusCode)
147 + assert.Equal(t, "0", resp.Resp.Header.Get("Content-Length"))
148 + assert.Equal(t, "text/plain", resp.Resp.Header.Get("Content-Type"))
149 + })
150 +
151 + t.Run("GET /ipfs/ipfs/{cid} returns redirect to the valid path", func(t *testing.T) {
152 + t.Parallel()
153 + resp := client.Get("/ipfs/ipfs/bafkqaaa?query=to-remember")
154 + assert.Contains(t,
155 + resp.Body,
156 + `<meta http-equiv="refresh" content="10;url=/ipfs/bafkqaaa?query=to-remember" />`,
157 + )
158 + assert.Contains(t,
159 + resp.Body,
160 + `<link rel="canonical" href="/ipfs/bafkqaaa?query=to-remember" />`,
161 + )
162 + })
163 + })
164 +
165 + t.Run("IPNS", func(t *testing.T) {
166 + t.Parallel()
167 + node.IPFS("name", "publish", "--allow-offline", cid)
168 +
169 + t.Run("GET invalid IPNS root returns 400 (Bad Request)", func(t *testing.T) {
170 + t.Parallel()
171 + resp := client.Get("/ipns/QmInvalid/pleaseDontAddMe")
172 + assert.Equal(t, 400, resp.StatusCode)
173 + })
174 +
175 + t.Run("GET IPNS path succeeds", func(t *testing.T) {
176 + t.Parallel()
177 + resp := client.Get("/ipns/{{.PeerID}}")
178 + assert.Equal(t, 200, resp.StatusCode)
179 + assert.Equal(t, "Hello Worlds!", resp.Body)
180 + })
181 +
182 + t.Run("GET /ipfs/ipns/{peerid} returns redirect to the valid path", func(t *testing.T) {
183 + t.Parallel()
184 + resp := client.Get("/ipfs/ipns/{{.PeerID}}?query=to-remember")
185 + peerID := node.PeerID().String()
186 + assert.Contains(t,
187 + resp.Body,
188 + fmt.Sprintf(`<meta http-equiv="refresh" content="10;url=/ipns/%s?query=to-remember" />`, peerID),
189 + )
190 + assert.Contains(t,
191 + resp.Body,
192 + fmt.Sprintf(`<link rel="canonical" href="/ipns/%s?query=to-remember" />`, peerID),
193 + )
194 +
195 + })
196 +
197 + })
198 +
199 + t.Run("GET invalid IPFS path errors", func(t *testing.T) {
200 + t.Parallel()
201 + assert.Equal(t, 400, client.Get("/ipfs/12345").StatusCode)
202 + })
203 +
204 + t.Run("GET invalid path errors", func(t *testing.T) {
205 + t.Parallel()
206 + assert.Equal(t, 404, client.Get("/12345").StatusCode)
207 + })
208 +
209 + // TODO: these tests that use the API URL shouldn't be part of gateway tests...
210 + t.Run("GET /webui returns 301 or 302", func(t *testing.T) {
211 + t.Parallel()
212 + resp := node.APIClient().DisableRedirects().Get("/webui")
213 + assert.Contains(t, []int{302, 301}, resp.StatusCode)
214 + })
215 +
216 + t.Run("GET /webui/ returns 301 or 302", func(t *testing.T) {
217 + t.Parallel()
218 + resp := node.APIClient().DisableRedirects().Get("/webui/")
219 + assert.Contains(t, []int{302, 301}, resp.StatusCode)
220 + })
221 +
222 + t.Run("GET /logs returns logs", func(t *testing.T) {
223 + t.Parallel()
224 + apiClient := node.APIClient()
225 + reqURL := apiClient.BuildURL("/logs")
226 +
227 + ctx, cancel := context.WithCancel(context.Background())
228 + defer cancel()
229 +
230 + req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
231 + require.NoError(t, err)
232 +
233 + resp, err := apiClient.Client.Do(req)
234 + require.NoError(t, err)
235 + defer resp.Body.Close()
236 +
237 + // read the first line of the output and parse its JSON
238 + dec := json.NewDecoder(resp.Body)
239 + event := struct{ Event string }{}
240 + err = dec.Decode(&event)
241 + require.NoError(t, err)
242 +
243 + assert.Equal(t, "log API client connected", event.Event)
244 + })
245 +
246 + t.Run("POST /api/v0/version succeeds", func(t *testing.T) {
247 + t.Parallel()
248 + resp := node.APIClient().Post("/api/v0/version", nil)
249 + assert.Equal(t, 200, resp.StatusCode)
250 +
251 + assert.Len(t, resp.Resp.TransferEncoding, 1)
252 + assert.Equal(t, "chunked", resp.Resp.TransferEncoding[0])
253 +
254 + vers := struct{ Version string }{}
255 + err := json.Unmarshal([]byte(resp.Body), &vers)
256 + require.NoError(t, err)
257 + assert.NotEmpty(t, vers.Version)
258 + })
259 +
260 + t.Run("pprof", func(t *testing.T) {
261 + t.Parallel()
262 + node := harness.NewT(t).NewNode().Init().StartDaemon()
263 + apiClient := node.APIClient()
264 + t.Run("mutex", func(t *testing.T) {
265 + t.Parallel()
266 + t.Run("setting the mutex fraction works (negative so it doesn't enable)", func(t *testing.T) {
267 + t.Parallel()
268 + resp := apiClient.Post("/debug/pprof-mutex/?fraction=-1", nil)
269 + assert.Equal(t, 200, resp.StatusCode)
270 + })
271 + t.Run("mutex endpoint doesn't accept a string as an argument", func(t *testing.T) {
272 + t.Parallel()
273 + resp := apiClient.Post("/debug/pprof-mutex/?fraction=that_is_a_string", nil)
274 + assert.Equal(t, 400, resp.StatusCode)
275 + })
276 + t.Run("mutex endpoint returns 405 on GET", func(t *testing.T) {
277 + t.Parallel()
278 + resp := apiClient.Get("/debug/pprof-mutex/?fraction=-1")
279 + assert.Equal(t, 405, resp.StatusCode)
280 + })
281 + })
282 + t.Run("block", func(t *testing.T) {
283 + t.Parallel()
284 + t.Run("setting the block profiler rate works (0 so it doesn't enable)", func(t *testing.T) {
285 + t.Parallel()
286 + resp := apiClient.Post("/debug/pprof-block/?rate=0", nil)
287 + assert.Equal(t, 200, resp.StatusCode)
288 + })
289 + t.Run("block profiler endpoint doesn't accept a string as an argument", func(t *testing.T) {
290 + t.Parallel()
291 + resp := apiClient.Post("/debug/pprof-block/?rate=that_is_a_string", nil)
292 + assert.Equal(t, 400, resp.StatusCode)
293 + })
294 + t.Run("block profiler endpoint returns 405 on GET", func(t *testing.T) {
295 + t.Parallel()
296 + resp := apiClient.Get("/debug/pprof-block/?rate=0")
297 + assert.Equal(t, 405, resp.StatusCode)
298 + })
299 + })
300 + })
301 +
302 + t.Run("index content types", func(t *testing.T) {
303 + t.Parallel()
304 + h := harness.NewT(t)
305 + node := h.NewNode().Init().StartDaemon()
306 +
307 + h.WriteFile("index/index.html", "<p></p>")
308 + cid := node.IPFS("add", "-Q", "-r", filepath.Join(h.Dir, "index")).Stderr.Trimmed()
309 +
310 + apiClient := node.APIClient()
311 + apiClient.TemplateData = map[string]string{"CID": cid}
312 +
313 + t.Run("GET index.html has correct content type", func(t *testing.T) {
314 + t.Parallel()
315 + res := apiClient.Get("/ipfs/{{.CID}}/")
316 + assert.Equal(t, "text/html; charset=utf-8", res.Resp.Header.Get("Content-Type"))
317 + })
318 +
319 + t.Run("HEAD index.html has no content", func(t *testing.T) {
320 + t.Parallel()
321 + res := apiClient.Head("/ipfs/{{.CID}}/")
322 + assert.Equal(t, "", res.Body)
323 + assert.Equal(t, "", res.Resp.Header.Get("Content-Length"))
324 + })
325 + })
326 +
327 + t.Run("readonly API", func(t *testing.T) {
328 + t.Parallel()
329 +
330 + client := node.GatewayClient()
331 +
332 + fileContents := "12345"
333 + h.WriteFile("readonly/dir/test", fileContents)
334 + cids := node.IPFS("add", "-r", "-q", filepath.Join(h.Dir, "readonly/dir")).Stdout.Lines()
335 +
336 + rootCID := cids[len(cids)-1]
337 + client.TemplateData = map[string]string{"RootCID": rootCID}
338 +
339 + t.Run("Get IPFS directory file through readonly API succeeds", func(t *testing.T) {
340 + t.Parallel()
341 + resp := client.Get("/api/v0/cat?arg={{.RootCID}}/test")
342 + assert.Equal(t, 200, resp.StatusCode)
343 + assert.Equal(t, fileContents, resp.Body)
344 + })
345 +
346 + t.Run("refs IPFS directory file through readonly API succeeds", func(t *testing.T) {
347 + t.Parallel()
348 + resp := client.Get("/api/v0/refs?arg={{.RootCID}}/test")
349 + assert.Equal(t, 200, resp.StatusCode)
350 + })
351 +
352 + t.Run("test gateway API is sanitized", func(t *testing.T) {
353 + t.Parallel()
354 + for _, cmd := range []string{
355 + "add",
356 + "block/put",
357 + "bootstrap",
358 + "config",
359 + "dag/put",
360 + "dag/import",
361 + "dht",
362 + "diag",
363 + "id",
364 + "mount",
365 + "name/publish",
366 + "object/put",
367 + "object/new",
368 + "object/patch",
369 + "pin",
370 + "ping",
371 + "repo",
372 + "stats",
373 + "swarm",
374 + "file",
375 + "update",
376 + "bitswap",
377 + } {
378 + t.Run(cmd, func(t *testing.T) {
379 + cmd := cmd
380 + t.Parallel()
381 + assert.Equal(t, 404, client.Get("/api/v0/"+cmd).StatusCode)
382 + })
383 + }
384 + })
385 + })
386 +
387 + t.Run("refs/local", func(t *testing.T) {
388 + t.Parallel()
389 + gatewayAddr := URLStrToMultiaddr(node.GatewayURL())
390 + res := node.RunIPFS("--api", gatewayAddr.String(), "refs", "local")
391 + assert.Equal(t,
392 + `Error: invalid path "local": selected encoding not supported`,
393 + res.Stderr.Trimmed(),
394 + )
395 + })
396 +
397 + t.Run("raw leaves node", func(t *testing.T) {
398 + t.Parallel()
399 + contents := "This is RAW!"
400 + cid := node.IPFSAddStr(contents, "--raw-leaves")
401 + assert.Equal(t, contents, client.Get("/ipfs/"+cid).Body)
402 + })
403 +
404 + t.Run("compact blocks", func(t *testing.T) {
405 + t.Parallel()
406 + block1 := "\x0a\x09\x08\x02\x12\x03\x66\x6f\x6f\x18\x03"
407 + block2 := "\x0a\x04\x08\x02\x18\x06\x12\x24\x0a\x22\x12\x20\xcf\x92\xfd\xef\xcd\xc3\x4c\xac\x00\x9c" +
408 + "\x8b\x05\xeb\x66\x2b\xe0\x61\x8d\xb9\xde\x55\xec\xd4\x27\x85\xe9\xec\x67\x12\xf8\xdf\x65" +
409 + "\x12\x24\x0a\x22\x12\x20\xcf\x92\xfd\xef\xcd\xc3\x4c\xac\x00\x9c\x8b\x05\xeb\x66\x2b\xe0" +
410 + "\x61\x8d\xb9\xde\x55\xec\xd4\x27\x85\xe9\xec\x67\x12\xf8\xdf\x65"
411 +
412 + node.PipeStrToIPFS(block1, "block", "put")
413 + block2CID := node.PipeStrToIPFS(block2, "block", "put", "--cid-codec=dag-pb").Stdout.Trimmed()
414 +
415 + resp := client.Get("/ipfs/" + block2CID)
416 + assert.Equal(t, 200, resp.StatusCode)
417 + assert.Equal(t, "foofoo", resp.Body)
418 + })
419 +
420 + t.Run("verify gateway file", func(t *testing.T) {
421 + t.Parallel()
422 + r := regexp.MustCompile(`Gateway \(readonly\) server listening on (?P<addr>.+)\s`)
423 + matches := r.FindStringSubmatch(node.Daemon.Stdout.String())
424 + ma, err := multiaddr.NewMultiaddr(matches[1])
425 + require.NoError(t, err)
426 + netAddr, err := manet.ToNetAddr(ma)
427 + require.NoError(t, err)
428 + expURL := "http://" + netAddr.String()
429 +
430 + b, err := os.ReadFile(filepath.Join(node.Dir, "gateway"))
431 + require.NoError(t, err)
432 +
433 + assert.Equal(t, expURL, string(b))
434 + })
435 +
436 + t.Run("verify gateway file diallable while on unspecified", func(t *testing.T) {
437 + t.Parallel()
438 + node := harness.NewT(t).NewNode().Init()
439 + node.UpdateConfig(func(cfg *config.Config) {
440 + cfg.Addresses.Gateway = config.Strings{"/ip4/127.0.0.1/tcp/32563"}
441 + })
442 + node.StartDaemon()
443 +
444 + b, err := os.ReadFile(filepath.Join(node.Dir, "gateway"))
445 + require.NoError(t, err)
446 +
447 + assert.Equal(t, "http://127.0.0.1:32563", string(b))
448 + })
449 +
450 + t.Run("NoFetch", func(t *testing.T) {
451 + t.Parallel()
452 + nodes := harness.NewT(t).NewNodes(2).Init()
453 + node1 := nodes[0]
454 + node2 := nodes[1]
455 +
456 + node1.UpdateConfig(func(cfg *config.Config) {
457 + cfg.Gateway.NoFetch = true
458 + })
459 +
460 + nodes.StartDaemons().Connect()
461 +
462 + t.Run("not present", func(t *testing.T) {
463 + cidFoo := node2.IPFSAddStr("foo")
464 +
465 + t.Run("not present key from node 1", func(t *testing.T) {
466 + t.Parallel()
467 + assert.Equal(t, 404, node1.GatewayClient().Get("/ipfs/"+cidFoo).StatusCode)
468 + })
469 +
470 + t.Run("not present IPNS key from node 1", func(t *testing.T) {
471 + t.Parallel()
472 + assert.Equal(t, 400, node1.GatewayClient().Get("/ipns/"+node2.PeerID().String()).StatusCode)
473 + })
474 + })
475 +
476 + t.Run("present", func(t *testing.T) {
477 + cidBar := node1.IPFSAddStr("bar")
478 +
479 + t.Run("present key from node 1", func(t *testing.T) {
480 + t.Parallel()
481 + assert.Equal(t, 200, node1.GatewayClient().Get("/ipfs/"+cidBar).StatusCode)
482 + })
483 +
484 + t.Run("present IPNS key from node 1", func(t *testing.T) {
485 + t.Parallel()
486 + node2.IPFS("name", "publish", "/ipfs/"+cidBar)
487 + assert.Equal(t, 200, node1.GatewayClient().Get("/ipns/"+node2.PeerID().String()).StatusCode)
488 +
489 + })
490 + })
491 + })
492 +}
test/cli/harness/harness.go
+8 -5
@@ -119,15 +119,19 @@ func (h *Harness) TempFile() *os.File {
119 }
120
121 // WriteFile writes a file given a filename and its contents.
122 -// The filename should be a relative path.
122 +// The filename must be a relative path, or this panics.
123 func (h *Harness) WriteFile(filename, contents string) {
124 if filepath.IsAbs(filename) {
125 log.Panicf("%s must be a relative path", filename)
126 }
127 absPath := filepath.Join(h.Runner.Dir, filename)
128 - err := os.WriteFile(absPath, []byte(contents), 0644)
128 + err := os.MkdirAll(filepath.Dir(absPath), 0777)
129 if err != nil {
130 - log.Panicf("writing '%s' ('%s'): %s", filename, absPath, err.Error())
130 + log.Panicf("creating intermediate dirs for %q: %s", filename, err.Error())
131 + }
132 + err = os.WriteFile(absPath, []byte(contents), 0644)
133 + if err != nil {
134 + log.Panicf("writing %q (%q): %s", filename, absPath, err.Error())
135 }
136 }
137
@@ -140,8 +144,7 @@ func WaitForFile(path string, timeout time.Duration) error {
144 for {
145 select {
146 case <-timer.C:
143 - end := time.Now()
144 - return fmt.Errorf("timeout waiting for %s after %v", path, end.Sub(start))
147 + return fmt.Errorf("timeout waiting for %s after %v", path, time.Since(start))
148 case <-ticker.C:
149 _, err := os.Stat(path)
150 if err == nil {
test/cli/harness/http_client.go new
+116
@@ -0,0 +1,116 @@
1 +package harness
2 +
3 +import (
4 + "io"
5 + "net/http"
6 + "strings"
7 + "text/template"
8 + "time"
9 +)
10 +
11 +// HTTPClient is an HTTP client with some conveniences for testing.
12 +// URLs are constructed from a base URL.
13 +// The response body is buffered into a string.
14 +// Internal errors cause panics so that tests don't need to check errors.
15 +// The paths are evaluated as Go templates for readable string interpolation.
16 +type HTTPClient struct {
17 + Client *http.Client
18 + BaseURL string
19 +
20 + Timeout time.Duration
21 + TemplateData any
22 +}
23 +
24 +type HTTPResponse struct {
25 + Body string
26 + StatusCode int
27 + Headers http.Header
28 +
29 + // The raw response. The body will be closed on this response.
30 + Resp *http.Response
31 +}
32 +
33 +func (c *HTTPClient) WithHeader(k, v string) func(h *http.Request) {
34 + return func(h *http.Request) {
35 + h.Header.Add(k, v)
36 + }
37 +}
38 +
39 +func (c *HTTPClient) DisableRedirects() *HTTPClient {
40 + c.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
41 + return http.ErrUseLastResponse
42 + }
43 + return c
44 +}
45 +
46 +// Do executes the request unchanged.
47 +func (c *HTTPClient) Do(req *http.Request) *HTTPResponse {
48 + log.Debugf("making HTTP req %s to %q with headers %+v", req.Method, req.URL.String(), req.Header)
49 + resp, err := c.Client.Do(req)
50 + if resp != nil && resp.Body != nil {
51 + defer resp.Body.Close()
52 + }
53 + if err != nil {
54 + panic(err)
55 + }
56 + bodyStr, err := io.ReadAll(resp.Body)
57 + if err != nil {
58 + panic(err)
59 + }
60 +
61 + return &HTTPResponse{
62 + Body: string(bodyStr),
63 + StatusCode: resp.StatusCode,
64 + Headers: resp.Header,
65 + Resp: resp,
66 + }
67 +}
68 +
69 +// BuildURL constructs a request URL from the given path by interpolating the string and then appending it to the base URL.
70 +func (c *HTTPClient) BuildURL(urlPath string) string {
71 + sb := &strings.Builder{}
72 + err := template.Must(template.New("test").Parse(urlPath)).Execute(sb, c.TemplateData)
73 + if err != nil {
74 + panic(err)
75 + }
76 + renderedPath := sb.String()
77 + return c.BaseURL + renderedPath
78 +}
79 +
80 +func (c *HTTPClient) Get(urlPath string, opts ...func(*http.Request)) *HTTPResponse {
81 + req, err := http.NewRequest(http.MethodGet, c.BuildURL(urlPath), nil)
82 + if err != nil {
83 + panic(err)
84 + }
85 + for _, o := range opts {
86 + o(req)
87 + }
88 + return c.Do(req)
89 +}
90 +
91 +func (c *HTTPClient) Post(urlPath string, body io.Reader, opts ...func(*http.Request)) *HTTPResponse {
92 + req, err := http.NewRequest(http.MethodPost, c.BuildURL(urlPath), body)
93 + if err != nil {
94 + panic(err)
95 + }
96 + for _, o := range opts {
97 + o(req)
98 + }
99 + return c.Do(req)
100 +}
101 +
102 +func (c *HTTPClient) PostStr(urlpath, body string, opts ...func(*http.Request)) *HTTPResponse {
103 + r := strings.NewReader(body)
104 + return c.Post(urlpath, r, opts...)
105 +}
106 +
107 +func (c *HTTPClient) Head(urlPath string, opts ...func(*http.Request)) *HTTPResponse {
108 + req, err := http.NewRequest(http.MethodHead, c.BuildURL(urlPath), nil)
109 + if err != nil {
110 + panic(err)
111 + }
112 + for _, o := range opts {
113 + o(req)
114 + }
115 + return c.Do(req)
116 +}
test/cli/harness/node.go
+68 -12
@@ -5,6 +5,7 @@ import (
5 "errors"
6 "fmt"
7 "io"
8 + "io/fs"
9 "net/http"
10 "os"
11 "os/exec"
@@ -19,6 +20,7 @@ import (
20 serial "github.com/ipfs/kubo/config/serialize"
21 "github.com/libp2p/go-libp2p/core/peer"
22 "github.com/multiformats/go-multiaddr"
23 + manet "github.com/multiformats/go-multiaddr/net"
24 )
25
26 var log = logging.Logger("testharness")
@@ -29,14 +31,15 @@ type Node struct {
31 ID int
32 Dir string
33
32 - APIListenAddr multiaddr.Multiaddr
33 - SwarmAddr multiaddr.Multiaddr
34 - EnableMDNS bool
34 + APIListenAddr multiaddr.Multiaddr
35 + GatewayListenAddr multiaddr.Multiaddr
36 + SwarmAddr multiaddr.Multiaddr
37 + EnableMDNS bool
38
39 IPFSBin string
40 Runner *Runner
41
39 - daemon *RunResult
42 + Daemon *RunResult
43 }
44
45 func BuildNode(ipfsBin, baseDir string, id int) *Node {
@@ -134,11 +137,19 @@ func (n *Node) Init(ipfsArgs ...string) *Node {
137 n.APIListenAddr = apiAddr
138 }
139
140 + if n.GatewayListenAddr == nil {
141 + gatewayAddr, err := multiaddr.NewMultiaddr("/ip4/127.0.0.1/tcp/0")
142 + if err != nil {
143 + panic(err)
144 + }
145 + n.GatewayListenAddr = gatewayAddr
146 + }
147 +
148 n.UpdateConfig(func(cfg *config.Config) {
149 cfg.Bootstrap = []string{}
150 cfg.Addresses.Swarm = []string{n.SwarmAddr.String()}
151 cfg.Addresses.API = []string{n.APIListenAddr.String()}
141 - cfg.Addresses.Gateway = []string{""}
152 + cfg.Addresses.Gateway = []string{n.GatewayListenAddr.String()}
153 cfg.Swarm.DisableNatPortMap = true
154 cfg.Discovery.MDNS.Enabled = n.EnableMDNS
155 })
@@ -159,7 +170,7 @@ func (n *Node) StartDaemon(ipfsArgs ...string) *Node {
170 RunFunc: (*exec.Cmd).Start,
171 })
172
162 - n.daemon = &res
173 + n.Daemon = &res
174
175 log.Debugf("node %d started, checking API", n.ID)
176 n.WaitOnAPI()
@@ -167,7 +178,7 @@ func (n *Node) StartDaemon(ipfsArgs ...string) *Node {
178 }
179
180 func (n *Node) signalAndWait(watch <-chan struct{}, signal os.Signal, t time.Duration) bool {
170 - err := n.daemon.Cmd.Process.Signal(signal)
181 + err := n.Daemon.Cmd.Process.Signal(signal)
182 if err != nil {
183 if errors.Is(err, os.ErrProcessDone) {
184 log.Debugf("process for node %d has already finished", n.ID)
@@ -187,13 +198,13 @@ func (n *Node) signalAndWait(watch <-chan struct{}, signal os.Signal, t time.Dur
198
199 func (n *Node) StopDaemon() *Node {
200 log.Debugf("stopping node %d", n.ID)
190 - if n.daemon == nil {
201 + if n.Daemon == nil {
202 log.Debugf("didn't stop node %d since no daemon present", n.ID)
203 return n
204 }
205 watch := make(chan struct{}, 1)
206 go func() {
196 - _, _ = n.daemon.Cmd.Process.Wait()
207 + _, _ = n.Daemon.Cmd.Process.Wait()
208 watch <- struct{}{}
209 }()
210 log.Debugf("signaling node %d with SIGTERM", n.ID)
@@ -224,6 +235,15 @@ func (n *Node) APIAddr() multiaddr.Multiaddr {
235 return ma
236 }
237
238 +func (n *Node) APIURL() string {
239 + apiAddr := n.APIAddr()
240 + netAddr, err := manet.ToNetAddr(apiAddr)
241 + if err != nil {
242 + panic(err)
243 + }
244 + return "http://" + netAddr.String()
245 +}
246 +
247 func (n *Node) TryAPIAddr() (multiaddr.Multiaddr, error) {
248 b, err := os.ReadFile(filepath.Join(n.Dir, "api"))
249 if err != nil {
@@ -305,20 +325,21 @@ func (n *Node) WaitOnAPI() *Node {
325 log.Debugf("waiting on API for node %d", n.ID)
326 for i := 0; i < 50; i++ {
327 if n.checkAPI() {
328 + log.Debugf("daemon API found, daemon stdout: %s", n.Daemon.Stdout.String())
329 return n
330 }
331 time.Sleep(400 * time.Millisecond)
332 }
312 - log.Panicf("node %d with peer ID %s failed to come online: \n%s\n\n%s", n.ID, n.PeerID(), n.daemon.Stderr.String(), n.daemon.Stdout.String())
333 + log.Panicf("node %d with peer ID %s failed to come online: \n%s\n\n%s", n.ID, n.PeerID(), n.Daemon.Stderr.String(), n.Daemon.Stdout.String())
334 return n
335 }
336
337 func (n *Node) IsAlive() bool {
317 - if n.daemon == nil || n.daemon.Cmd == nil || n.daemon.Cmd.Process == nil {
338 + if n.Daemon == nil || n.Daemon.Cmd == nil || n.Daemon.Cmd.Process == nil {
339 return false
340 }
341 log.Debugf("signaling node %d daemon process for liveness check", n.ID)
321 - err := n.daemon.Cmd.Process.Signal(syscall.Signal(0))
342 + err := n.Daemon.Cmd.Process.Signal(syscall.Signal(0))
343 if err == nil {
344 log.Debugf("node %d daemon is alive", n.ID)
345 return true
@@ -381,3 +402,38 @@ func (n *Node) Peers() []multiaddr.Multiaddr {
402 }
403 return addrs
404 }
405 +
406 +// GatewayURL waits for the gateway file and then returns its contents or times out.
407 +func (n *Node) GatewayURL() string {
408 + timer := time.NewTimer(1 * time.Second)
409 + defer timer.Stop()
410 + for {
411 + select {
412 + case <-timer.C:
413 + panic("timeout waiting for gateway file")
414 + default:
415 + b, err := os.ReadFile(filepath.Join(n.Dir, "gateway"))
416 + if err == nil {
417 + return strings.TrimSpace(string(b))
418 + }
419 + if !errors.Is(err, fs.ErrNotExist) {
420 + panic(err)
421 + }
422 + time.Sleep(1 * time.Millisecond)
423 + }
424 + }
425 +}
426 +
427 +func (n *Node) GatewayClient() *HTTPClient {
428 + return &HTTPClient{
429 + Client: http.DefaultClient,
430 + BaseURL: n.GatewayURL(),
431 + }
432 +}
433 +
434 +func (n *Node) APIClient() *HTTPClient {
435 + return &HTTPClient{
436 + Client: http.DefaultClient,
437 + BaseURL: n.APIURL(),
438 + }
439 +}
test/cli/harness/nodes.go
+19 -2
@@ -1,6 +1,8 @@
1 package harness
2
3 import (
4 + "sync"
5 +
6 "github.com/multiformats/go-multiaddr"
7 )
8
@@ -15,14 +17,22 @@ func (n Nodes) Init(args ...string) Nodes {
17 }
18
19 func (n Nodes) Connect() Nodes {
20 + wg := sync.WaitGroup{}
21 for i, node := range n {
22 for j, otherNode := range n {
23 if i == j {
24 continue
25 }
23 - node.Connect(otherNode)
26 + node := node
27 + otherNode := otherNode
28 + wg.Add(1)
29 + go func() {
30 + defer wg.Done()
31 + node.Connect(otherNode)
32 + }()
33 }
34 }
35 + wg.Wait()
36 for _, node := range n {
37 firstPeer := node.Peers()[0]
38 if _, err := firstPeer.ValueForProtocol(multiaddr.P_P2P); err != nil {
@@ -33,9 +43,16 @@ func (n Nodes) Connect() Nodes {
43 }
44
45 func (n Nodes) StartDaemons() Nodes {
46 + wg := sync.WaitGroup{}
47 for _, node := range n {
37 - node.StartDaemon()
48 + wg.Add(1)
49 + node := node
50 + go func() {
51 + defer wg.Done()
52 + node.StartDaemon()
53 + }()
54 }
55 + wg.Wait()
56 return n
57 }
58
test/cli/testutils/strings.go
+24
@@ -3,7 +3,13 @@ package testutils
3 import (
4 "bufio"
5 "fmt"
6 + "net"
7 + "net/netip"
8 + "net/url"
9 "strings"
10 +
11 + "github.com/multiformats/go-multiaddr"
12 + manet "github.com/multiformats/go-multiaddr/net"
13 )
14
15 // StrCat takes a bunch of strings or string slices
@@ -51,3 +57,21 @@ func SplitLines(s string) []string {
57 }
58 return lines
59 }
60 +
61 +// URLStrToMultiaddr converts a URL string like http://localhost:80 to a multiaddr.
62 +func URLStrToMultiaddr(u string) multiaddr.Multiaddr {
63 + parsedURL, err := url.Parse(u)
64 + if err != nil {
65 + panic(err)
66 + }
67 + addrPort, err := netip.ParseAddrPort(parsedURL.Host)
68 + if err != nil {
69 + panic(err)
70 + }
71 + tcpAddr := net.TCPAddrFromAddrPort(addrPort)
72 + ma, err := manet.FromNetAddr(tcpAddr)
73 + if err != nil {
74 + panic(err)
75 + }
76 + return ma
77 +}
test/sharness/t0110-gateway-data/foo.block deleted
-2
@@ -1,2 +0,0 @@
1 -
2 - foo
\ No newline at end of file
test/sharness/t0110-gateway-data/foofoo.block
Binary files a/test/sharness/t0110-gateway-data/foofoo.block and /dev/null differ
test/sharness/t0110-gateway.sh deleted
-357
@@ -1,357 +0,0 @@
1 -#!/usr/bin/env bash
2 -#
3 -# Copyright (c) 2015 Matt Bell
4 -# MIT Licensed; see the LICENSE file in this repository.
5 -#
6 -
7 -test_description="Test HTTP Gateway"
8 -
9 -. lib/test-lib.sh
10 -
11 -test_init_ipfs
12 -test_launch_ipfs_daemon
13 -
14 -port=$GWAY_PORT
15 -apiport=$API_PORT
16 -
17 -# TODO check both 5001 and 5002.
18 -# 5001 should have a readable gateway (part of the API)
19 -# 5002 should have a readable gateway (using ipfs config Addresses.Gateway)
20 -# but ideally we should only write the tests once. so maybe we need to
21 -# define a function to test a gateway, and do so for each port.
22 -# for now we check 5001 here as 5002 will be checked in gateway-writable.
23 -
24 -test_expect_success "Make a file to test with" '
25 - echo "Hello Worlds!" >expected &&
26 - HASH=$(ipfs add -q expected) ||
27 - test_fsh cat daemon_err
28 -'
29 -
30 -test_expect_success "GET IPFS path succeeds" '
31 - curl -sfo actual "http://127.0.0.1:$port/ipfs/$HASH"
32 -'
33 -
34 -test_expect_success "GET IPFS path with explicit ?filename succeeds with proper header" "
35 - curl -fo actual -D actual_headers 'http://127.0.0.1:$port/ipfs/$HASH?filename=testтест.pdf' &&
36 - grep -F 'Content-Disposition: inline; filename=\"test____.pdf\"; filename*=UTF-8'\'\''test%D1%82%D0%B5%D1%81%D1%82.pdf' actual_headers
37 -"
38 -
39 -test_expect_success "GET IPFS path with explicit ?filename and &download=true succeeds with proper header" "
40 - curl -fo actual -D actual_headers 'http://127.0.0.1:$port/ipfs/$HASH?filename=testтест.mp4&download=true' &&
41 - grep -F 'Content-Disposition: attachment; filename=\"test____.mp4\"; filename*=UTF-8'\'\''test%D1%82%D0%B5%D1%81%D1%82.mp4' actual_headers
42 -"
43 -
44 -# https://github.com/ipfs/go-ipfs/issues/4025#issuecomment-342250616
45 -test_expect_success "GET for Service Worker registration outside of an IPFS content root errors" "
46 - curl -H 'Service-Worker: script' -svX GET 'http://127.0.0.1:$port/ipfs/$HASH?filename=sw.js' > curl_sw_out 2>&1 &&
47 - grep 'HTTP/1.1 400 Bad Request' curl_sw_out &&
48 - grep 'navigator.serviceWorker: registration is not allowed for this scope' curl_sw_out
49 -"
50 -
51 -test_expect_success "GET IPFS path output looks good" '
52 - test_cmp expected actual &&
53 - rm actual
54 -'
55 -
56 -test_expect_success "GET IPFS directory path succeeds" '
57 - mkdir -p dir/dirwithindex &&
58 - echo "12345" >dir/test &&
59 - echo "hello i am a webpage" >dir/dirwithindex/index.html &&
60 - ipfs add -r -q dir >actual &&
61 - HASH2=$(tail -n 1 actual) &&
62 - curl -sf "http://127.0.0.1:$port/ipfs/$HASH2"
63 -'
64 -
65 -test_expect_success "GET IPFS directory file succeeds" '
66 - curl -sfo actual "http://127.0.0.1:$port/ipfs/$HASH2/test"
67 -'
68 -
69 -test_expect_success "GET IPFS directory file output looks good" '
70 - test_cmp dir/test actual
71 -'
72 -
73 -test_expect_success "GET IPFS directory with index.html returns redirect to add trailing slash" "
74 - curl -sI -o response_without_slash \"http://127.0.0.1:$port/ipfs/$HASH2/dirwithindex?query=to-remember\" &&
75 - test_should_contain \"HTTP/1.1 301 Moved Permanently\" response_without_slash &&
76 - test_should_contain \"Location: /ipfs/$HASH2/dirwithindex/?query=to-remember\" response_without_slash
77 -"
78 -
79 -# This enables go get to parse go-import meta tags from index.html files stored in IPFS
80 -# https://github.com/ipfs/kubo/pull/3963
81 -test_expect_success "GET IPFS directory with index.html and no trailing slash returns expected output when go-get is passed" "
82 - curl -s -o response_with_slash \"http://127.0.0.1:$port/ipfs/$HASH2/dirwithindex?go-get=1\" &&
83 - test_should_contain \"hello i am a webpage\" response_with_slash
84 -"
85 -
86 -test_expect_success "GET IPFS directory with index.html and trailing slash returns expected output" "
87 - curl -s -o response_with_slash \"http://127.0.0.1:$port/ipfs/$HASH2/dirwithindex/?query=to-remember\" &&
88 - test_should_contain \"hello i am a webpage\" response_with_slash
89 -"
90 -
91 -test_expect_success "GET IPFS nonexistent file returns 404 (Not Found)" '
92 - test_curl_resp_http_code "http://127.0.0.1:$port/ipfs/$HASH2/pleaseDontAddMe" "HTTP/1.1 404 Not Found"
93 -'
94 -
95 -test_expect_success "GET IPFS invalid CID returns 400 (Bad Request)" '
96 - test_curl_resp_http_code "http://127.0.0.1:$port/ipfs/QmInvalid/pleaseDontAddMe" "HTTP/1.1 400 Bad Request"
97 -'
98 -
99 -# https://github.com/ipfs/go-ipfs/issues/8230
100 -test_expect_success "GET IPFS inlined zero-length data object returns ok code (200)" '
101 - curl -sD - "http://127.0.0.1:$port/ipfs/bafkqaaa" > empty_ok_response &&
102 - test_should_contain "HTTP/1.1 200 OK" empty_ok_response &&
103 - test_should_contain "Content-Length: 0" empty_ok_response
104 -'
105 -
106 -# https://github.com/ipfs/kubo/issues/9238
107 -test_expect_success "GET IPFS inlined zero-length data object with byte range returns ok code (200)" '
108 - curl -sD - "http://127.0.0.1:$port/ipfs/bafkqaaa" -H "Range: bytes=0-1048575" > empty_ok_response &&
109 - test_should_contain "HTTP/1.1 200 OK" empty_ok_response &&
110 - test_should_contain "Content-Length: 0" empty_ok_response &&
111 - test_should_contain "Content-Type: text/plain" empty_ok_response
112 -'
113 -
114 -test_expect_success "GET /ipfs/ipfs/{cid} returns redirect to the valid path" '
115 - curl -sD - "http://127.0.0.1:$port/ipfs/ipfs/bafkqaaa?query=to-remember" > response_with_double_ipfs_ns &&
116 - test_should_contain "<meta http-equiv=\"refresh\" content=\"10;url=/ipfs/bafkqaaa?query=to-remember\" />" response_with_double_ipfs_ns &&
117 - test_should_contain "<link rel=\"canonical\" href=\"/ipfs/bafkqaaa?query=to-remember\" />" response_with_double_ipfs_ns
118 -'
119 -
120 -test_expect_success "GET invalid IPNS root returns 400 (Bad Request)" '
121 - test_curl_resp_http_code "http://127.0.0.1:$port/ipns/QmInvalid/pleaseDontAddMe" "HTTP/1.1 400 Bad Request"
122 -'
123 -
124 -test_expect_success "GET IPNS path succeeds" '
125 - ipfs name publish --allow-offline "$HASH" &&
126 - PEERID=$(ipfs config Identity.PeerID) &&
127 - test_check_peerid "$PEERID" &&
128 - curl -sfo actual "http://127.0.0.1:$port/ipns/$PEERID"
129 -'
130 -
131 -test_expect_success "GET IPNS path output looks good" '
132 - test_cmp expected actual
133 -'
134 -
135 -test_expect_success "GET /ipfs/ipns/{peerid} returns redirect to the valid path" '
136 - PEERID=$(ipfs config Identity.PeerID) &&
137 - curl -sD - "http://127.0.0.1:$port/ipfs/ipns/${PEERID}?query=to-remember" > response_with_ipfs_ipns_ns &&
138 - test_should_contain "<meta http-equiv=\"refresh\" content=\"10;url=/ipns/${PEERID}?query=to-remember\" />" response_with_ipfs_ipns_ns &&
139 - test_should_contain "<link rel=\"canonical\" href=\"/ipns/${PEERID}?query=to-remember\" />" response_with_ipfs_ipns_ns
140 -'
141 -
142 -test_expect_success "GET invalid IPFS path errors" '
143 - test_must_fail curl -sf "http://127.0.0.1:$port/ipfs/12345"
144 -'
145 -
146 -test_expect_success "GET invalid path errors" '
147 - test_must_fail curl -sf "http://127.0.0.1:$port/12345"
148 -'
149 -
150 -test_expect_success "GET /webui returns code expected" '
151 - test_curl_resp_http_code "http://127.0.0.1:$apiport/webui" "HTTP/1.1 302 Found" "HTTP/1.1 301 Moved Permanently"
152 -'
153 -
154 -test_expect_success "GET /webui/ returns code expected" '
155 - test_curl_resp_http_code "http://127.0.0.1:$apiport/webui/" "HTTP/1.1 302 Found" "HTTP/1.1 301 Moved Permanently"
156 -'
157 -
158 -test_expect_success "GET /logs returns logs" '
159 - test_expect_code 28 curl http://127.0.0.1:$apiport/logs -m1 > log_out
160 -'
161 -
162 -test_expect_success "log output looks good" '
163 - grep "log API client connected" log_out
164 -'
165 -
166 -test_expect_success "GET /api/v0/version succeeds" '
167 - curl -X POST -v "http://127.0.0.1:$apiport/api/v0/version" 2> version_out
168 -'
169 -
170 -test_expect_success "output only has one transfer encoding header" '
171 - grep "Transfer-Encoding: chunked" version_out | wc -l | xargs echo > tecount_out &&
172 - echo "1" > tecount_exp &&
173 - test_cmp tecount_out tecount_exp
174 -'
175 -
176 -curl_pprofmutex() {
177 - curl -f -X POST "http://127.0.0.1:$apiport/debug/pprof-mutex/?fraction=$1"
178 -}
179 -
180 -test_expect_success "set mutex fraction for pprof (negative so it doesn't enable)" '
181 - curl_pprofmutex -1
182 -'
183 -
184 -test_expect_success "test failure conditions of mutex pprof endpoint" '
185 - test_must_fail curl_pprofmutex &&
186 - test_must_fail curl_pprofmutex that_is_string &&
187 - test_must_fail curl -f -X GET "http://127.0.0.1:$apiport/debug/pprof-mutex/?fraction=-1"
188 -'
189 -
190 -curl_pprofblock() {
191 - curl -f -X POST "http://127.0.0.1:$apiport/debug/pprof-block/?rate=$1"
192 -}
193 -
194 -test_expect_success "set blocking profiler rate for pprof (0 so it doesn't enable)" '
195 - curl_pprofblock 0
196 -'
197 -
198 -test_expect_success "test failure conditions of mutex block endpoint" '
199 - test_must_fail curl_pprofblock &&
200 - test_must_fail curl_pprofblock that_is_string &&
201 - test_must_fail curl -f -X GET "http://127.0.0.1:$apiport/debug/pprof-block/?rate=0"
202 -'
203 -
204 -test_expect_success "setup index hash" '
205 - mkdir index &&
206 - echo "<p></p>" > index/index.html &&
207 - INDEXHASH=$(ipfs add -Q -r index)
208 - echo index: $INDEXHASH
209 -'
210 -
211 -test_expect_success "GET 'index.html' has correct content type" '
212 - curl -I "http://127.0.0.1:$port/ipfs/$INDEXHASH/" > indexout
213 -'
214 -
215 -test_expect_success "output looks good" '
216 - grep "Content-Type: text/html" indexout
217 -'
218 -
219 -test_expect_success "HEAD 'index.html' has no content" '
220 - curl -X HEAD --max-time 1 http://127.0.0.1:$port/ipfs/$INDEXHASH/ > output;
221 - [ ! -s output ]
222 -'
223 -
224 -# test ipfs readonly api
225 -
226 -test_curl_gateway_api() {
227 - curl -sfo actual "http://127.0.0.1:$port/api/v0/$1"
228 -}
229 -
230 -test_expect_success "get IPFS directory file through readonly API succeeds" '
231 - test_curl_gateway_api "cat?arg=$HASH2/test"
232 -'
233 -
234 -test_expect_success "get IPFS directory file through readonly API output looks good" '
235 - test_cmp dir/test actual
236 -'
237 -
238 -test_expect_success "refs IPFS directory file through readonly API succeeds" '
239 - test_curl_gateway_api "refs?arg=$HASH2/test"
240 -'
241 -
242 -for cmd in add \
243 - block/put \
244 - bootstrap \
245 - config \
246 - dag/put \
247 - dag/import \
248 - dht \
249 - diag \
250 - id \
251 - mount \
252 - name/publish \
253 - object/put \
254 - object/new \
255 - object/patch \
256 - pin \
257 - ping \
258 - repo \
259 - stats \
260 - swarm \
261 - file \
262 - update \
263 - bitswap
264 -do
265 - test_expect_success "test gateway api is sanitized: $cmd" '
266 - test_curl_resp_http_code "http://127.0.0.1:$port/api/v0/$cmd" "HTTP/1.1 404 Not Found"
267 - '
268 -done
269 -
270 -# This one is different. `local` will be interpreted as a path if the command isn't defined.
271 -test_expect_success "test gateway api is sanitized: refs/local" '
272 - echo "Error: invalid path \"local\": selected encoding not supported" > refs_local_expected &&
273 - ! ipfs --api /ip4/127.0.0.1/tcp/$port refs local > refs_local_actual 2>&1 &&
274 - test_cmp refs_local_expected refs_local_actual
275 - '
276 -
277 -test_expect_success "create raw-leaves node" '
278 - echo "This is RAW!" > rfile &&
279 - echo "This is RAW!" | ipfs add --raw-leaves -q > rhash
280 -'
281 -
282 -test_expect_success "try fetching it from gateway" '
283 - curl http://127.0.0.1:$port/ipfs/$(cat rhash) > ffile &&
284 - test_cmp rfile ffile
285 -'
286 -
287 -test_expect_success "Add compact blocks" '
288 - ipfs block put ../t0110-gateway-data/foo.block &&
289 - FOO2_HASH=$(ipfs block put --cid-codec=dag-pb ../t0110-gateway-data/foofoo.block) &&
290 - printf "foofoo" > expected
291 -'
292 -
293 -test_expect_success "GET compact blocks succeeds" '
294 - curl -o actual "http://127.0.0.1:$port/ipfs/$FOO2_HASH" &&
295 - test_cmp expected actual
296 -'
297 -
298 -test_expect_success "Verify gateway file" '
299 - cat "$IPFS_PATH/gateway" > gateway_file_actual &&
300 - echo -n "http://$GWAY_ADDR" > gateway_daemon_actual &&
301 - test_cmp gateway_daemon_actual gateway_file_actual
302 -'
303 -
304 -test_kill_ipfs_daemon
305 -
306 -GWPORT=32563
307 -
308 -test_expect_success "Verify gateway file diallable while on unspecified" '
309 - ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/$GWPORT &&
310 - test_launch_ipfs_daemon &&
311 - cat "$IPFS_PATH/gateway" > gateway_file_actual &&
312 - echo -n "http://127.0.0.1:$GWPORT" > gateway_file_expected &&
313 - test_cmp gateway_file_expected gateway_file_actual
314 -'
315 -
316 -test_kill_ipfs_daemon
317 -
318 -test_expect_success "set up iptb testbed" '
319 - iptb testbed create -type localipfs -count 5 -force -init &&
320 - ipfsi 0 config Addresses.Gateway /ip4/127.0.0.1/tcp/$GWPORT &&
321 - PEERID_1=$(iptb attr get 1 id)
322 -'
323 -
324 -test_expect_success "set NoFetch to true in config of node 0" '
325 - ipfsi 0 config --bool=true Gateway.NoFetch true
326 -'
327 -
328 -test_expect_success "start ipfs nodes" '
329 - iptb start -wait &&
330 - iptb connect 0 1
331 -'
332 -
333 -test_expect_success "try fetching not present key from node 0" '
334 - FOO=$(echo "foo" | ipfsi 1 add -Q) &&
335 - test_expect_code 22 curl -f "http://127.0.0.1:$GWPORT/ipfs/$FOO"
336 -'
337 -
338 -test_expect_success "try fetching not present ipns key from node 0" '
339 - ipfsi 1 name publish /ipfs/$FOO &&
340 - test_expect_code 22 curl -f "http://127.0.0.1:$GWPORT/ipns/$PEERID_1"
341 -'
342 -
343 -test_expect_success "try fetching present key from node 0" '
344 - BAR=$(echo "bar" | ipfsi 0 add -Q) &&
345 - curl -f "http://127.0.0.1:$GWPORT/ipfs/$BAR"
346 -'
347 -
348 -test_expect_success "try fetching present ipns key from node 0" '
349 - ipfsi 1 name publish /ipfs/$BAR &&
350 - curl "http://127.0.0.1:$GWPORT/ipns/$PEERID_1"
351 -'
352 -
353 -test_expect_success "stop testbed" '
354 - iptb stop
355 -'
356 -
357 -test_done