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 }