github.com/ethersphere/bee/v2@v2.2.0/pkg/api/chunk_test.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package api_test 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/hex" 11 "errors" 12 "fmt" 13 "net/http" 14 "testing" 15 "time" 16 17 "github.com/ethersphere/bee/v2/pkg/crypto" 18 "github.com/ethersphere/bee/v2/pkg/log" 19 "github.com/ethersphere/bee/v2/pkg/postage" 20 mockbatchstore "github.com/ethersphere/bee/v2/pkg/postage/batchstore/mock" 21 mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock" 22 "github.com/ethersphere/bee/v2/pkg/spinlock" 23 mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" 24 25 "github.com/ethersphere/bee/v2/pkg/api" 26 "github.com/ethersphere/bee/v2/pkg/jsonhttp" 27 "github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest" 28 testingpostage "github.com/ethersphere/bee/v2/pkg/postage/testing" 29 testingc "github.com/ethersphere/bee/v2/pkg/storage/testing" 30 "github.com/ethersphere/bee/v2/pkg/swarm" 31 ) 32 33 // nolint:paralleltest,tparallel 34 // TestChunkUploadDownload uploads a chunk to an API that verifies the chunk according 35 // to a given validator, then tries to download the uploaded data. 36 func TestChunkUploadDownload(t *testing.T) { 37 t.Parallel() 38 39 var ( 40 chunksEndpoint = "/chunks" 41 chunksResource = func(a swarm.Address) string { return "/chunks/" + a.String() } 42 chunk = testingc.GenerateTestRandomChunk() 43 storerMock = mockstorer.New() 44 client, _, _, chanStorer = newTestServer(t, testServerOptions{ 45 Storer: storerMock, 46 Post: mockpost.New(mockpost.WithAcceptAll()), 47 DirectUpload: true, 48 }) 49 ) 50 51 t.Run("empty chunk", func(t *testing.T) { 52 jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusBadRequest, 53 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), 54 jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ 55 Message: "insufficient data length", 56 Code: http.StatusBadRequest, 57 }), 58 ) 59 }) 60 61 t.Run("ok", func(t *testing.T) { 62 tag, err := storerMock.NewSession() 63 if err != nil { 64 t.Fatalf("failed creating tag: %v", err) 65 } 66 67 jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusCreated, 68 jsonhttptest.WithRequestHeader(api.SwarmTagHeader, fmt.Sprintf("%d", tag.TagID)), 69 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), 70 jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), 71 jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}), 72 ) 73 74 has, err := storerMock.ChunkStore().Has(context.Background(), chunk.Address()) 75 if err != nil { 76 t.Fatal(err) 77 } 78 if !has { 79 t.Fatal("storer check root chunk reference: have none; want one") 80 } 81 82 // try to fetch the same chunk 83 jsonhttptest.Request(t, client, http.MethodGet, chunksResource(chunk.Address()), http.StatusOK, 84 jsonhttptest.WithExpectedResponse(chunk.Data()), 85 jsonhttptest.WithExpectedContentLength(len(chunk.Data())), 86 ) 87 }) 88 89 t.Run("direct upload ok", func(t *testing.T) { 90 jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusCreated, 91 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), 92 jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), 93 jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}), 94 ) 95 96 time.Sleep(time.Millisecond * 100) 97 err := spinlock.Wait(time.Second, func() bool { return chanStorer.Has(chunk.Address()) }) 98 if err != nil { 99 t.Fatal(err) 100 } 101 }) 102 } 103 104 // nolint:paralleltest,tparallel 105 func TestChunkHasHandler(t *testing.T) { 106 mockStorer := mockstorer.New() 107 testServer, _, _, _ := newTestServer(t, testServerOptions{ 108 Storer: mockStorer, 109 }) 110 111 key := swarm.MustParseHexAddress("aabbcc") 112 value := []byte("data data data") 113 114 err := mockStorer.Cache().Put(context.Background(), swarm.NewChunk(key, value)) 115 if err != nil { 116 t.Fatal(err) 117 } 118 119 t.Run("ok", func(t *testing.T) { 120 jsonhttptest.Request(t, testServer, http.MethodHead, "/chunks/"+key.String(), http.StatusOK, 121 jsonhttptest.WithNoResponseBody(), 122 ) 123 124 jsonhttptest.Request(t, testServer, http.MethodGet, "/chunks/"+key.String(), http.StatusOK, 125 jsonhttptest.WithExpectedResponse(value), 126 jsonhttptest.WithExpectedContentLength(len(value)), 127 ) 128 }) 129 130 t.Run("not found", func(t *testing.T) { 131 jsonhttptest.Request(t, testServer, http.MethodHead, "/chunks/abbbbb", http.StatusNotFound, 132 jsonhttptest.WithNoResponseBody()) 133 }) 134 135 t.Run("bad address", func(t *testing.T) { 136 jsonhttptest.Request(t, testServer, http.MethodHead, "/chunks/abcd1100zz", http.StatusBadRequest, 137 jsonhttptest.WithNoResponseBody()) 138 }) 139 } 140 141 func TestChunkHandlersInvalidInputs(t *testing.T) { 142 t.Parallel() 143 144 client, _, _, _ := newTestServer(t, testServerOptions{}) 145 146 tests := []struct { 147 name string 148 address string 149 want jsonhttp.StatusResponse 150 }{{ 151 name: "address odd hex string", 152 address: "123", 153 want: jsonhttp.StatusResponse{ 154 Code: http.StatusBadRequest, 155 Message: "invalid path params", 156 Reasons: []jsonhttp.Reason{ 157 { 158 Field: "address", 159 Error: api.ErrHexLength.Error(), 160 }, 161 }, 162 }, 163 }, { 164 name: "address invalid hex character", 165 address: "123G", 166 want: jsonhttp.StatusResponse{ 167 Code: http.StatusBadRequest, 168 Message: "invalid path params", 169 Reasons: []jsonhttp.Reason{ 170 { 171 Field: "address", 172 Error: api.HexInvalidByteError('G').Error(), 173 }, 174 }, 175 }, 176 }} 177 178 method := http.MethodGet 179 for _, tc := range tests { 180 tc := tc 181 t.Run(method+" "+tc.name, func(t *testing.T) { 182 t.Parallel() 183 184 jsonhttptest.Request(t, client, method, "/chunks/"+tc.address, tc.want.Code, 185 jsonhttptest.WithExpectedJSONResponse(tc.want), 186 ) 187 }) 188 } 189 } 190 191 func TestChunkInvalidParams(t *testing.T) { 192 t.Parallel() 193 194 var ( 195 chunksEndpoint = "/chunks" 196 chunk = testingc.GenerateTestRandomChunk() 197 storerMock = mockstorer.New() 198 logger = log.Noop 199 existsFn = func(id []byte) (bool, error) { 200 return false, errors.New("error") 201 } 202 ) 203 204 t.Run("batch unusable", func(t *testing.T) { 205 t.Parallel() 206 207 clientBatchUnusable, _, _, _ := newTestServer(t, testServerOptions{ 208 Storer: storerMock, 209 Logger: logger, 210 Post: mockpost.New(mockpost.WithAcceptAll()), 211 BatchStore: mockbatchstore.New(), 212 }) 213 jsonhttptest.Request(t, clientBatchUnusable, http.MethodPost, chunksEndpoint, http.StatusUnprocessableEntity, 214 jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"), 215 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), 216 jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), 217 ) 218 }) 219 220 t.Run("batch exists", func(t *testing.T) { 221 t.Parallel() 222 223 clientBatchExists, _, _, _ := newTestServer(t, testServerOptions{ 224 Storer: storerMock, 225 Logger: logger, 226 Post: mockpost.New(mockpost.WithAcceptAll()), 227 BatchStore: mockbatchstore.New(mockbatchstore.WithExistsFunc(existsFn)), 228 }) 229 jsonhttptest.Request(t, clientBatchExists, http.MethodPost, chunksEndpoint, http.StatusBadRequest, 230 jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"), 231 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), 232 jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), 233 ) 234 }) 235 236 t.Run("batch not found", func(t *testing.T) { 237 t.Parallel() 238 239 clientBatchNotFound, _, _, _ := newTestServer(t, testServerOptions{ 240 Storer: storerMock, 241 Logger: logger, 242 Post: mockpost.New(), 243 }) 244 jsonhttptest.Request(t, clientBatchNotFound, http.MethodPost, chunksEndpoint, http.StatusNotFound, 245 jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"), 246 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), 247 jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), 248 ) 249 }) 250 } 251 252 // // TestDirectChunkUpload tests that the direct upload endpoint give correct error message in dev mode 253 func TestChunkDirectUpload(t *testing.T) { 254 t.Parallel() 255 var ( 256 chunksEndpoint = "/chunks" 257 chunk = testingc.GenerateTestRandomChunk() 258 storerMock = mockstorer.New() 259 client, _, _, _ = newTestServer(t, testServerOptions{ 260 Storer: storerMock, 261 Post: mockpost.New(mockpost.WithAcceptAll()), 262 BeeMode: api.DevMode, 263 }) 264 ) 265 266 jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusBadRequest, 267 jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "false"), 268 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), 269 jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), 270 jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ 271 Message: api.ErrUnsupportedDevNodeOperation.Error(), 272 Code: http.StatusBadRequest, 273 }), 274 ) 275 } 276 277 // // TestPreSignedUpload tests that chunk can be uploaded with pre-signed postage stamp 278 func TestPreSignedUpload(t *testing.T) { 279 t.Parallel() 280 281 var ( 282 chunksEndpoint = "/chunks" 283 chunk = testingc.GenerateTestRandomChunk() 284 storerMock = mockstorer.New() 285 batchStore = mockbatchstore.New() 286 client, _, _, _ = newTestServer(t, testServerOptions{ 287 Storer: storerMock, 288 BatchStore: batchStore, 289 }) 290 ) 291 292 // generate random postage batch and stamp 293 key, _ := crypto.GenerateSecp256k1Key() 294 signer := crypto.NewDefaultSigner(key) 295 owner, _ := signer.EthereumAddress() 296 stamp := testingpostage.MustNewValidStamp(signer, chunk.Address()) 297 _ = batchStore.Save(&postage.Batch{ 298 ID: stamp.BatchID(), 299 Owner: owner.Bytes(), 300 }) 301 stampBytes, _ := stamp.MarshalBinary() 302 303 // read off inserted chunk 304 go func() { <-storerMock.PusherFeed() }() 305 306 jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusCreated, 307 jsonhttptest.WithRequestHeader(api.SwarmPostageStampHeader, hex.EncodeToString(stampBytes)), 308 jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), 309 ) 310 }