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  }