master
go 215 lines 5.29 KB
Raw
1 package corehttp
2
3 import (
4 "context"
5 "errors"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11
12 "github.com/ipfs/boxo/namesys"
13 version "github.com/ipfs/kubo"
14 "github.com/ipfs/kubo/core"
15 "github.com/ipfs/kubo/core/coreapi"
16 "github.com/ipfs/kubo/repo"
17 "github.com/stretchr/testify/assert"
18
19 "github.com/ipfs/boxo/path"
20 "github.com/ipfs/go-datastore"
21 syncds "github.com/ipfs/go-datastore/sync"
22 "github.com/ipfs/kubo/config"
23 iface "github.com/ipfs/kubo/core/coreiface"
24 ci "github.com/libp2p/go-libp2p/core/crypto"
25 )
26
27 type mockNamesys map[string]path.Path
28
29 func (m mockNamesys) Resolve(ctx context.Context, p path.Path, opts ...namesys.ResolveOption) (namesys.Result, error) {
30 cfg := namesys.DefaultResolveOptions()
31 for _, o := range opts {
32 o(&cfg)
33 }
34 depth := cfg.Depth
35 if depth == namesys.UnlimitedDepth {
36 // max uint
37 depth = ^uint(0)
38 }
39 var (
40 value path.Path
41 )
42 name := path.SegmentsToString(p.Segments()[:2]...)
43 for strings.HasPrefix(name, "/ipns/") {
44 if depth == 0 {
45 return namesys.Result{Path: value}, namesys.ErrResolveRecursion
46 }
47 depth--
48
49 v, ok := m[name]
50 if !ok {
51 return namesys.Result{}, namesys.ErrResolveFailed
52 }
53 value = v
54 name = value.String()
55 }
56
57 value, err := path.Join(value, p.Segments()[2:]...)
58 return namesys.Result{Path: value}, err
59 }
60
61 func (m mockNamesys) ResolveAsync(ctx context.Context, p path.Path, opts ...namesys.ResolveOption) <-chan namesys.AsyncResult {
62 out := make(chan namesys.AsyncResult, 1)
63 res, err := m.Resolve(ctx, p, opts...)
64 out <- namesys.AsyncResult{Path: res.Path, TTL: res.TTL, LastMod: res.LastMod, Err: err}
65 close(out)
66 return out
67 }
68
69 func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Path, opts ...namesys.PublishOption) error {
70 return errors.New("not implemented for mockNamesys")
71 }
72
73 func (m mockNamesys) GetResolver(subs string) (namesys.Resolver, bool) {
74 return nil, false
75 }
76
77 func newNodeWithMockNamesys(ns mockNamesys) (*core.IpfsNode, error) {
78 c := config.Config{
79 Identity: config.Identity{
80 PeerID: "QmTFauExutTsy4XP6JbMFcw2Wa9645HJt2bTqL6qYDCKfe", // required by offline node
81 },
82 }
83 r := &repo.Mock{
84 C: c,
85 D: syncds.MutexWrap(datastore.NewMapDatastore()),
86 }
87 n, err := core.NewNode(context.Background(), &core.BuildCfg{Repo: r})
88 if err != nil {
89 return nil, err
90 }
91 n.Namesys = ns
92 return n, nil
93 }
94
95 type delegatedHandler struct {
96 http.Handler
97 }
98
99 func (dh *delegatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
100 dh.Handler.ServeHTTP(w, r)
101 }
102
103 func doWithoutRedirect(req *http.Request) (*http.Response, error) {
104 tag := "without-redirect"
105 c := &http.Client{
106 CheckRedirect: func(req *http.Request, via []*http.Request) error {
107 return errors.New(tag)
108 },
109 }
110 res, err := c.Do(req)
111 if err != nil && !strings.Contains(err.Error(), tag) {
112 return nil, err
113 }
114 return res, nil
115 }
116
117 func newTestServerAndNode(t *testing.T, ns mockNamesys) (*httptest.Server, iface.CoreAPI, context.Context) {
118 n, err := newNodeWithMockNamesys(ns)
119 if err != nil {
120 t.Fatal(err)
121 }
122
123 // need this variable here since we need to construct handler with
124 // listener, and server with handler. yay cycles.
125 dh := &delegatedHandler{}
126 ts := httptest.NewServer(dh)
127 t.Cleanup(func() { ts.Close() })
128
129 dh.Handler, err = MakeHandler(n,
130 ts.Listener,
131 HostnameOption(),
132 GatewayOption("/ipfs", "/ipns"),
133 VersionOption(),
134 )
135 if err != nil {
136 t.Fatal(err)
137 }
138
139 api, err := coreapi.NewCoreAPI(n)
140 if err != nil {
141 t.Fatal(err)
142 }
143
144 return ts, api, n.Context()
145 }
146
147 func TestVersion(t *testing.T) {
148 version.CurrentCommit = "theshortcommithash"
149
150 ns := mockNamesys{}
151 ts, _, _ := newTestServerAndNode(t, ns)
152 t.Logf("test server url: %s", ts.URL)
153
154 req, err := http.NewRequest(http.MethodGet, ts.URL+"/version", nil)
155 if err != nil {
156 t.Fatal(err)
157 }
158
159 res, err := doWithoutRedirect(req)
160 if err != nil {
161 t.Fatal(err)
162 }
163 body, err := io.ReadAll(res.Body)
164 if err != nil {
165 t.Fatalf("error reading response: %s", err)
166 }
167 s := string(body)
168
169 if !strings.Contains(s, "Commit: theshortcommithash") {
170 t.Fatalf("response doesn't contain commit:\n%s", s)
171 }
172
173 if !strings.Contains(s, "Client Version: "+version.GetUserAgentVersion()) {
174 t.Fatalf("response doesn't contain client version:\n%s", s)
175 }
176 }
177
178 func TestDeserializedResponsesInheritance(t *testing.T) {
179 for _, testCase := range []struct {
180 globalSetting config.Flag
181 gatewaySetting config.Flag
182 expectedGatewaySetting bool
183 }{
184 {config.True, config.Default, true},
185 {config.False, config.Default, false},
186 {config.False, config.True, true},
187 {config.True, config.False, false},
188 } {
189 c := config.Config{
190 Identity: config.Identity{
191 PeerID: "QmTFauExutTsy4XP6JbMFcw2Wa9645HJt2bTqL6qYDCKfe", // required by offline node
192 },
193 Gateway: config.Gateway{
194 DeserializedResponses: testCase.globalSetting,
195 PublicGateways: map[string]*config.GatewaySpec{
196 "example.com": {
197 DeserializedResponses: testCase.gatewaySetting,
198 },
199 },
200 },
201 }
202 r := &repo.Mock{
203 C: c,
204 D: syncds.MutexWrap(datastore.NewMapDatastore()),
205 }
206 n, err := core.NewNode(context.Background(), &core.BuildCfg{Repo: r})
207 assert.NoError(t, err)
208
209 gwCfg, _, err := getGatewayConfig(n)
210 assert.NoError(t, err)
211
212 assert.Contains(t, gwCfg.PublicGateways, "example.com")
213 assert.Equal(t, testCase.expectedGatewaySetting, gwCfg.PublicGateways["example.com"].DeserializedResponses)
214 }
215 }