github.com/ethersphere/bee/v2@v2.2.0/pkg/api/feed_test.go (about)

     1  // Copyright 2021 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  	"context"
     9  	"encoding/binary"
    10  	"encoding/hex"
    11  	"errors"
    12  	"fmt"
    13  	"math/big"
    14  	"net/http"
    15  	"testing"
    16  
    17  	"github.com/ethersphere/bee/v2/pkg/api"
    18  	"github.com/ethersphere/bee/v2/pkg/feeds"
    19  	"github.com/ethersphere/bee/v2/pkg/file/loadsave"
    20  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    21  	"github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest"
    22  	"github.com/ethersphere/bee/v2/pkg/log"
    23  	"github.com/ethersphere/bee/v2/pkg/manifest"
    24  	"github.com/ethersphere/bee/v2/pkg/postage"
    25  	mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock"
    26  	testingsoc "github.com/ethersphere/bee/v2/pkg/soc/testing"
    27  	mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock"
    28  	"github.com/ethersphere/bee/v2/pkg/swarm"
    29  )
    30  
    31  const ownerString = "8d3766440f0d7b949a5e32995d09619a7f86e632"
    32  
    33  var expReference = swarm.MustParseHexAddress("891a1d1c8436c792d02fc2e8883fef7ab387eaeaacd25aa9f518be7be7856d54")
    34  
    35  func TestFeed_Get(t *testing.T) {
    36  	t.Parallel()
    37  
    38  	var (
    39  		feedResource = func(owner, topic, at string) string {
    40  			if at != "" {
    41  				return fmt.Sprintf("/feeds/%s/%s?at=%s", owner, topic, at)
    42  			}
    43  			return fmt.Sprintf("/feeds/%s/%s", owner, topic)
    44  		}
    45  		mockStorer = mockstorer.New()
    46  	)
    47  
    48  	t.Run("with at", func(t *testing.T) {
    49  		t.Parallel()
    50  
    51  		var (
    52  			timestamp       = int64(12121212)
    53  			ch              = toChunk(t, uint64(timestamp), expReference.Bytes())
    54  			look            = newMockLookup(12, 0, ch, nil, &id{}, &id{})
    55  			factory         = newMockFactory(look)
    56  			idBytes, _      = (&id{}).MarshalBinary()
    57  			client, _, _, _ = newTestServer(t, testServerOptions{
    58  				Storer: mockStorer,
    59  				Feeds:  factory,
    60  			})
    61  		)
    62  
    63  		jsonhttptest.Request(t, client, http.MethodGet, feedResource(ownerString, "aabbcc", "12"), http.StatusOK,
    64  			jsonhttptest.WithExpectedJSONResponse(api.FeedReferenceResponse{Reference: expReference}),
    65  			jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
    66  		)
    67  	})
    68  
    69  	t.Run("latest", func(t *testing.T) {
    70  		t.Parallel()
    71  
    72  		var (
    73  			timestamp  = int64(12121212)
    74  			ch         = toChunk(t, uint64(timestamp), expReference.Bytes())
    75  			look       = newMockLookup(-1, 2, ch, nil, &id{}, &id{})
    76  			factory    = newMockFactory(look)
    77  			idBytes, _ = (&id{}).MarshalBinary()
    78  
    79  			client, _, _, _ = newTestServer(t, testServerOptions{
    80  				Storer: mockStorer,
    81  				Feeds:  factory,
    82  			})
    83  		)
    84  
    85  		jsonhttptest.Request(t, client, http.MethodGet, feedResource(ownerString, "aabbcc", ""), http.StatusOK,
    86  			jsonhttptest.WithExpectedJSONResponse(api.FeedReferenceResponse{Reference: expReference}),
    87  			jsonhttptest.WithExpectedResponseHeader(api.SwarmFeedIndexHeader, hex.EncodeToString(idBytes)),
    88  		)
    89  	})
    90  }
    91  
    92  // nolint:paralleltest
    93  func TestFeed_Post(t *testing.T) {
    94  	// post to owner, tpoic, then expect a reference
    95  	// get the reference from the store, unmarshal to a
    96  	// manifest entry and make sure all metadata correct
    97  	var (
    98  		logger          = log.Noop
    99  		topic           = "aabbcc"
   100  		mp              = mockpost.New(mockpost.WithIssuer(postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)))
   101  		mockStorer      = mockstorer.New()
   102  		client, _, _, _ = newTestServer(t, testServerOptions{
   103  			Storer: mockStorer,
   104  			Logger: logger,
   105  			Post:   mp,
   106  		})
   107  		url = fmt.Sprintf("/feeds/%s/%s?type=%s", ownerString, topic, "sequence")
   108  	)
   109  	t.Run("ok", func(t *testing.T) {
   110  		jsonhttptest.Request(t, client, http.MethodPost, url, http.StatusCreated,
   111  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   112  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   113  			jsonhttptest.WithExpectedJSONResponse(api.FeedReferenceResponse{
   114  				Reference: expReference,
   115  			}),
   116  		)
   117  
   118  		ls := loadsave.NewReadonly(mockStorer.ChunkStore())
   119  		i, err := manifest.NewMantarayManifestReference(expReference, ls)
   120  		if err != nil {
   121  			t.Fatal(err)
   122  		}
   123  		e, err := i.Lookup(context.Background(), "/")
   124  		if err != nil {
   125  			t.Fatal(err)
   126  		}
   127  
   128  		meta := e.Metadata()
   129  		if e := meta[api.FeedMetadataEntryOwner]; e != ownerString {
   130  			t.Fatalf("owner mismatch. got %s want %s", e, ownerString)
   131  		}
   132  		if e := meta[api.FeedMetadataEntryTopic]; e != topic {
   133  			t.Fatalf("topic mismatch. got %s want %s", e, topic)
   134  		}
   135  		if e := meta[api.FeedMetadataEntryType]; e != "Sequence" {
   136  			t.Fatalf("type mismatch. got %s want %s", e, "Sequence")
   137  		}
   138  	})
   139  	t.Run("postage", func(t *testing.T) {
   140  		t.Run("err - bad batch", func(t *testing.T) {
   141  			hexbatch := hex.EncodeToString(batchInvalid)
   142  			jsonhttptest.Request(t, client, http.MethodPost, url, http.StatusNotFound,
   143  				jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, hexbatch),
   144  				jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
   145  					Message: "batch with id not found",
   146  					Code:    http.StatusNotFound,
   147  				}))
   148  		})
   149  
   150  		t.Run("ok - batch zeros", func(t *testing.T) {
   151  			hexbatch := hex.EncodeToString(batchOk)
   152  			jsonhttptest.Request(t, client, http.MethodPost, url, http.StatusCreated,
   153  				jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   154  				jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, hexbatch),
   155  			)
   156  		})
   157  		t.Run("bad request - batch empty", func(t *testing.T) {
   158  			hexbatch := hex.EncodeToString(batchEmpty)
   159  			jsonhttptest.Request(t, client, http.MethodPost, url, http.StatusBadRequest,
   160  				jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, hexbatch),
   161  			)
   162  		})
   163  	})
   164  
   165  }
   166  
   167  // TestDirectUploadFeed tests that the direct upload endpoint give correct error message in dev mode
   168  func TestFeedDirectUpload(t *testing.T) {
   169  	t.Parallel()
   170  	var (
   171  		topic           = "aabbcc"
   172  		mp              = mockpost.New(mockpost.WithIssuer(postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)))
   173  		mockStorer      = mockstorer.New()
   174  		client, _, _, _ = newTestServer(t, testServerOptions{
   175  			Storer:  mockStorer,
   176  			Post:    mp,
   177  			BeeMode: api.DevMode,
   178  		})
   179  		url = fmt.Sprintf("/feeds/%s/%s?type=%s", ownerString, topic, "sequence")
   180  	)
   181  	jsonhttptest.Request(t, client, http.MethodPost, url, http.StatusBadRequest,
   182  		jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "false"),
   183  		jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   184  		jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
   185  			Message: api.ErrUnsupportedDevNodeOperation.Error(),
   186  			Code:    http.StatusBadRequest,
   187  		}),
   188  	)
   189  }
   190  
   191  type factoryMock struct {
   192  	sequenceCalled bool
   193  	epochCalled    bool
   194  	feed           *feeds.Feed
   195  	lookup         feeds.Lookup
   196  }
   197  
   198  func newMockFactory(mockLookup feeds.Lookup) *factoryMock {
   199  	return &factoryMock{lookup: mockLookup}
   200  }
   201  
   202  func (f *factoryMock) NewLookup(t feeds.Type, feed *feeds.Feed) (feeds.Lookup, error) {
   203  	switch t {
   204  	case feeds.Sequence:
   205  		f.sequenceCalled = true
   206  	case feeds.Epoch:
   207  		f.epochCalled = true
   208  	}
   209  	f.feed = feed
   210  	return f.lookup, nil
   211  }
   212  
   213  type mockLookup struct {
   214  	at        int64
   215  	after     uint64
   216  	chunk     swarm.Chunk
   217  	err       error
   218  	cur, next feeds.Index
   219  }
   220  
   221  func newMockLookup(at int64, after uint64, ch swarm.Chunk, err error, cur, next feeds.Index) *mockLookup {
   222  	return &mockLookup{at: at, after: after, chunk: ch, err: err, cur: cur, next: next}
   223  }
   224  
   225  func (l *mockLookup) At(_ context.Context, at int64, after uint64) (swarm.Chunk, feeds.Index, feeds.Index, error) {
   226  	if l.at == -1 {
   227  		// shortcut to ignore the value in the call since time.Now() is a moving target
   228  		return l.chunk, l.cur, l.next, nil
   229  	}
   230  	if at == l.at && after == l.after {
   231  		return l.chunk, l.cur, l.next, nil
   232  	}
   233  	return nil, nil, nil, errors.New("no feed update found")
   234  }
   235  
   236  func toChunk(t *testing.T, at uint64, payload []byte) swarm.Chunk {
   237  	t.Helper()
   238  
   239  	ts := make([]byte, 8)
   240  	binary.BigEndian.PutUint64(ts, at)
   241  	content := append(ts, payload...)
   242  
   243  	s := testingsoc.GenerateMockSOC(t, content)
   244  	return s.Chunk()
   245  }
   246  
   247  type id struct{}
   248  
   249  func (i *id) MarshalBinary() ([]byte, error) {
   250  	return []byte("accd"), nil
   251  }
   252  
   253  func (i *id) String() string {
   254  	return "44237"
   255  }
   256  
   257  func (*id) Next(last int64, at uint64) feeds.Index {
   258  	return &id{}
   259  }