| 1 | package rpc |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "net/http" |
| 7 | "net/http/httptest" |
| 8 | "strconv" |
| 9 | "strings" |
| 10 | "sync" |
| 11 | "testing" |
| 12 | "time" |
| 13 | |
| 14 | "github.com/ipfs/boxo/path" |
| 15 | "github.com/ipfs/kubo/config" |
| 16 | iface "github.com/ipfs/kubo/core/coreiface" |
| 17 | "github.com/ipfs/kubo/core/coreiface/tests" |
| 18 | "github.com/ipfs/kubo/test/cli/harness" |
| 19 | ma "github.com/multiformats/go-multiaddr" |
| 20 | ) |
| 21 | |
| 22 | type NodeProvider struct{} |
| 23 | |
| 24 | func (np NodeProvider) MakeAPISwarm(t *testing.T, ctx context.Context, fullIdentity, online bool, n int) ([]iface.CoreAPI, error) { |
| 25 | h := harness.NewT(t) |
| 26 | |
| 27 | apis := make([]iface.CoreAPI, n) |
| 28 | nodes := h.NewNodes(n) |
| 29 | |
| 30 | var wg, zero sync.WaitGroup |
| 31 | zeroNode := nodes[0] |
| 32 | wg.Add(len(apis)) |
| 33 | zero.Add(1) |
| 34 | |
| 35 | var errs []error |
| 36 | var errsLk sync.Mutex |
| 37 | |
| 38 | for i, n := range nodes { |
| 39 | go func(i int, n *harness.Node) { |
| 40 | if err := func() error { |
| 41 | defer wg.Done() |
| 42 | var err error |
| 43 | |
| 44 | n.Init("--empty-repo") |
| 45 | |
| 46 | c := n.ReadConfig() |
| 47 | c.Experimental.FilestoreEnabled = true |
| 48 | // only provide things we pin. Allows to test |
| 49 | // provide operations. |
| 50 | c.Provide.Strategy = config.NewOptionalString("roots") |
| 51 | n.WriteConfig(c) |
| 52 | n.StartDaemon("--enable-pubsub-experiment", "--offline="+strconv.FormatBool(!online)) |
| 53 | |
| 54 | if online { |
| 55 | if i > 0 { |
| 56 | zero.Wait() |
| 57 | n.Connect(zeroNode) |
| 58 | } else { |
| 59 | zero.Done() |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | apiMaddr, err := n.TryAPIAddr() |
| 64 | if err != nil { |
| 65 | return err |
| 66 | } |
| 67 | |
| 68 | api, err := NewApi(apiMaddr) |
| 69 | if err != nil { |
| 70 | return err |
| 71 | } |
| 72 | apis[i] = api |
| 73 | |
| 74 | // empty node is pinned even with --empty-repo, we don't want that |
| 75 | emptyNode, err := path.NewPath("/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn") |
| 76 | if err != nil { |
| 77 | return err |
| 78 | } |
| 79 | |
| 80 | if err := api.Pin().Rm(ctx, emptyNode); err != nil { |
| 81 | return err |
| 82 | } |
| 83 | return nil |
| 84 | }(); err != nil { |
| 85 | errsLk.Lock() |
| 86 | errs = append(errs, err) |
| 87 | errsLk.Unlock() |
| 88 | } |
| 89 | }(i, n) |
| 90 | } |
| 91 | |
| 92 | wg.Wait() |
| 93 | |
| 94 | return apis, errors.Join(errs...) |
| 95 | } |
| 96 | |
| 97 | func TestHttpApi(t *testing.T) { |
| 98 | t.Parallel() |
| 99 | |
| 100 | tests.TestApi(NodeProvider{})(t) |
| 101 | } |
| 102 | |
| 103 | func Test_NewURLApiWithClient_With_Headers(t *testing.T) { |
| 104 | t.Parallel() |
| 105 | |
| 106 | var ( |
| 107 | headerToTest = "Test-Header" |
| 108 | expectedHeaderValue = "thisisaheadertest" |
| 109 | ) |
| 110 | ts := httptest.NewServer( |
| 111 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 112 | val := r.Header.Get(headerToTest) |
| 113 | if val != expectedHeaderValue { |
| 114 | w.WriteHeader(400) |
| 115 | return |
| 116 | } |
| 117 | http.ServeContent(w, r, "", time.Now(), strings.NewReader("test")) |
| 118 | }), |
| 119 | ) |
| 120 | defer ts.Close() |
| 121 | api, err := NewURLApiWithClient(ts.URL, &http.Client{ |
| 122 | Transport: &http.Transport{ |
| 123 | Proxy: http.ProxyFromEnvironment, |
| 124 | DisableKeepAlives: true, |
| 125 | }, |
| 126 | }) |
| 127 | if err != nil { |
| 128 | t.Fatal(err) |
| 129 | } |
| 130 | api.Headers.Set(headerToTest, expectedHeaderValue) |
| 131 | p, err := path.NewPath("/ipfs/QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv") |
| 132 | if err != nil { |
| 133 | t.Fatal(err) |
| 134 | } |
| 135 | if err := api.Pin().Rm(context.Background(), p); err != nil { |
| 136 | t.Fatal(err) |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | func Test_NewURLApiWithClient_HTTP_Variant(t *testing.T) { |
| 141 | t.Parallel() |
| 142 | |
| 143 | testcases := []struct { |
| 144 | address string |
| 145 | expected string |
| 146 | }{ |
| 147 | {address: "/ip4/127.0.0.1/tcp/80", expected: "http://127.0.0.1:80"}, |
| 148 | {address: "/ip4/127.0.0.1/tcp/443/tls", expected: "https://127.0.0.1:443"}, |
| 149 | {address: "/ip4/127.0.0.1/tcp/443/https", expected: "https://127.0.0.1:443"}, |
| 150 | {address: "/ip4/127.0.0.1/tcp/443/tls/http", expected: "https://127.0.0.1:443"}, |
| 151 | } |
| 152 | |
| 153 | for _, tc := range testcases { |
| 154 | address, err := ma.NewMultiaddr(tc.address) |
| 155 | if err != nil { |
| 156 | t.Fatal(err) |
| 157 | } |
| 158 | |
| 159 | api, err := NewApiWithClient(address, &http.Client{}) |
| 160 | if err != nil { |
| 161 | t.Fatal(err) |
| 162 | } |
| 163 | |
| 164 | if api.url != tc.expected { |
| 165 | t.Errorf("Expected = %s; got %s", tc.expected, api.url) |
| 166 | } |
| 167 | } |
| 168 | } |