github.com/ethersphere/bee/v2@v2.2.0/pkg/api/bzz_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  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"mime"
    14  	"mime/multipart"
    15  	"net/http"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  
    20  	"github.com/ethersphere/bee/v2/pkg/api"
    21  	"github.com/ethersphere/bee/v2/pkg/file/loadsave"
    22  	"github.com/ethersphere/bee/v2/pkg/file/redundancy"
    23  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    24  	"github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest"
    25  	"github.com/ethersphere/bee/v2/pkg/log"
    26  	"github.com/ethersphere/bee/v2/pkg/manifest"
    27  	mockbatchstore "github.com/ethersphere/bee/v2/pkg/postage/batchstore/mock"
    28  	mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock"
    29  	"github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore"
    30  	mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock"
    31  	"github.com/ethersphere/bee/v2/pkg/swarm"
    32  	"github.com/ethersphere/bee/v2/pkg/util/testutil/pseudorand"
    33  )
    34  
    35  // nolint:paralleltest,tparallel,thelper
    36  
    37  // TestBzzUploadDownloadWithRedundancy tests the API for upload and download files
    38  // with all combinations of redundancy level, encryption and size (levels, i.e., the
    39  //
    40  //	height of the swarm hash tree).
    41  //
    42  // This is a variation on the same play as TestJoinerRedundancy
    43  // but here the tested scenario is simplified since we are not testing the intricacies of
    44  // download strategies, but only correct parameter passing and correct recovery functionality
    45  //
    46  // The test cases have the following structure:
    47  //
    48  //  1. upload a file with a given redundancy level and encryption
    49  //
    50  //  2. [positive test] download the file by the reference returned by the upload API response
    51  //     This uses range queries to target specific (number of) chunks of the file structure.
    52  //     During path traversal in the swarm hash tree, the underlying mocksore (forgetting)
    53  //     is in 'recording' mode, flagging all the retrieved chunks as chunks to forget.
    54  //     This is to simulate the scenario where some of the chunks are not available/lost
    55  //     NOTE: For this to work one needs to switch off lookaheadbuffer functionality
    56  //     (see langos pkg)
    57  //
    58  //  3. [negative test] attempt at downloading the file using once again the same root hash
    59  //     and the same redundancy strategy to find the file inaccessible after forgetting.
    60  //
    61  //  4. [positive test] attempt at downloading the file using a strategy that allows for
    62  //     using redundancy to reconstruct the file and find the file recoverable.
    63  //
    64  // nolint:thelper
    65  func TestBzzUploadDownloadWithRedundancy_FLAKY(t *testing.T) {
    66  	t.Skip("flaky")
    67  	t.Parallel()
    68  	fileUploadResource := "/bzz"
    69  	fileDownloadResource := func(addr string) string { return "/bzz/" + addr + "/" }
    70  
    71  	testRedundancy := func(t *testing.T, rLevel redundancy.Level, encrypt bool, levels int, chunkCnt int, shardCnt int, parityCnt int) {
    72  		t.Helper()
    73  		seed, err := pseudorand.NewSeed()
    74  		if err != nil {
    75  			t.Fatal(err)
    76  		}
    77  		store := mockstorer.NewForgettingStore(inmemchunkstore.New())
    78  		storerMock := mockstorer.NewWithChunkStore(store)
    79  		client, _, _, _ := newTestServer(t, testServerOptions{
    80  			Storer: storerMock,
    81  			Logger: log.Noop,
    82  			Post:   mockpost.New(mockpost.WithAcceptAll()),
    83  		})
    84  
    85  		dataReader := pseudorand.NewReader(seed, chunkCnt*swarm.ChunkSize)
    86  
    87  		var refResponse api.BzzUploadResponse
    88  		jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource,
    89  			http.StatusCreated,
    90  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "True"),
    91  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
    92  			jsonhttptest.WithRequestBody(dataReader),
    93  			jsonhttptest.WithRequestHeader(api.SwarmEncryptHeader, fmt.Sprintf("%t", encrypt)),
    94  			jsonhttptest.WithRequestHeader(api.SwarmRedundancyLevelHeader, fmt.Sprintf("%d", rLevel)),
    95  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "image/jpeg; charset=utf-8"),
    96  			jsonhttptest.WithUnmarshalJSONResponse(&refResponse),
    97  		)
    98  
    99  		t.Run("download multiple ranges without redundancy should succeed", func(t *testing.T) {
   100  			// the underlying chunk store is in recording mode, so all chunks retrieved
   101  			// in this test will be forgotten in the subsequent ones.
   102  			store.Record()
   103  			defer store.Unrecord()
   104  			// we intend to forget as many chunks as possible for the given redundancy level
   105  			forget := parityCnt
   106  			if parityCnt > shardCnt {
   107  				forget = shardCnt
   108  			}
   109  			if levels == 1 {
   110  				forget = 2
   111  			}
   112  			start, end := 420, 450
   113  			gap := swarm.ChunkSize
   114  			for j := 2; j < levels; j++ {
   115  				gap *= shardCnt
   116  			}
   117  			ranges := make([][2]int, forget)
   118  			for i := 0; i < forget; i++ {
   119  				pre := i * gap
   120  				ranges[i] = [2]int{pre + start, pre + end}
   121  			}
   122  			rangeHeader, want := createRangeHeader(dataReader, ranges)
   123  
   124  			var body []byte
   125  			respHeaders := jsonhttptest.Request(t, client, http.MethodGet,
   126  				fileDownloadResource(refResponse.Reference.String()),
   127  				http.StatusPartialContent,
   128  				jsonhttptest.WithRequestHeader(api.RangeHeader, rangeHeader),
   129  				jsonhttptest.WithRequestHeader(api.SwarmLookAheadBufferSizeHeader, "0"),
   130  				// set for the replicas so that no replica gets deleted
   131  				jsonhttptest.WithRequestHeader(api.SwarmRedundancyLevelHeader, "0"),
   132  				jsonhttptest.WithRequestHeader(api.SwarmRedundancyStrategyHeader, "0"),
   133  				jsonhttptest.WithRequestHeader(api.SwarmRedundancyFallbackModeHeader, "false"),
   134  				jsonhttptest.WithPutResponseBody(&body),
   135  			)
   136  
   137  			got := parseRangeParts(t, respHeaders.Get(api.ContentTypeHeader), body)
   138  
   139  			if len(got) != len(want) {
   140  				t.Fatalf("got %v parts, want %v parts", len(got), len(want))
   141  			}
   142  			for i := 0; i < len(want); i++ {
   143  				if !bytes.Equal(got[i], want[i]) {
   144  					t.Errorf("part %v: got %q, want %q", i, string(got[i]), string(want[i]))
   145  				}
   146  			}
   147  		})
   148  
   149  		t.Run("download without redundancy should NOT succeed", func(t *testing.T) {
   150  			if rLevel == 0 {
   151  				t.Skip("NA")
   152  			}
   153  			req, err := http.NewRequestWithContext(context.Background(), "GET", fileDownloadResource(refResponse.Reference.String()), nil)
   154  			if err != nil {
   155  				t.Fatal(err)
   156  			}
   157  			req.Header.Set(api.SwarmRedundancyStrategyHeader, "0")
   158  			req.Header.Set(api.SwarmRedundancyFallbackModeHeader, "false")
   159  
   160  			resp, err := client.Do(req)
   161  			if err != nil {
   162  				t.Fatal(err)
   163  			}
   164  			defer resp.Body.Close()
   165  
   166  			if resp.StatusCode != http.StatusOK {
   167  				t.Fatalf("expected status %d; got %d", http.StatusOK, resp.StatusCode)
   168  			}
   169  			_, err = dataReader.Seek(0, io.SeekStart)
   170  			if err != nil {
   171  				t.Fatal(err)
   172  			}
   173  			ok, err := dataReader.Equal(resp.Body)
   174  			if err != nil {
   175  				t.Fatal(err)
   176  			}
   177  			if ok {
   178  				t.Fatal("there should be missing data")
   179  			}
   180  		})
   181  
   182  		t.Run("download with redundancy should succeed", func(t *testing.T) {
   183  			req, err := http.NewRequestWithContext(context.Background(), "GET", fileDownloadResource(refResponse.Reference.String()), nil)
   184  			if err != nil {
   185  				t.Fatal(err)
   186  			}
   187  			req.Header.Set(api.SwarmRedundancyStrategyHeader, "3")
   188  			req.Header.Set(api.SwarmRedundancyFallbackModeHeader, "true")
   189  
   190  			resp, err := client.Do(req)
   191  			if err != nil {
   192  				t.Fatal(err)
   193  			}
   194  			defer resp.Body.Close()
   195  
   196  			if resp.StatusCode != http.StatusOK {
   197  				t.Fatalf("expected status %d; got %d", http.StatusOK, resp.StatusCode)
   198  			}
   199  			_, err = dataReader.Seek(0, io.SeekStart)
   200  			if err != nil {
   201  				t.Fatal(err)
   202  			}
   203  			ok, err := dataReader.Equal(resp.Body)
   204  			if err != nil {
   205  				t.Fatal(err)
   206  			}
   207  			if !ok {
   208  				t.Fatalf("content mismatch")
   209  			}
   210  		})
   211  	}
   212  	for _, rLevel := range []redundancy.Level{1, 2, 3, 4} {
   213  		rLevel := rLevel
   214  		t.Run(fmt.Sprintf("level=%d", rLevel), func(t *testing.T) {
   215  			for _, encrypt := range []bool{false, true} {
   216  				encrypt := encrypt
   217  				shardCnt := rLevel.GetMaxShards()
   218  				parityCnt := rLevel.GetParities(shardCnt)
   219  				if encrypt {
   220  					shardCnt = rLevel.GetMaxEncShards()
   221  					parityCnt = rLevel.GetEncParities(shardCnt)
   222  				}
   223  				for _, levels := range []int{1, 2, 3} {
   224  					chunkCnt := 1
   225  					switch levels {
   226  					case 1:
   227  						chunkCnt = 2
   228  					case 2:
   229  						chunkCnt = shardCnt + 1
   230  					case 3:
   231  						chunkCnt = shardCnt*shardCnt + 1
   232  					}
   233  					levels := levels
   234  					t.Run(fmt.Sprintf("encrypt=%v levels=%d chunks=%d", encrypt, levels, chunkCnt), func(t *testing.T) {
   235  						if levels > 2 && (encrypt == (rLevel%2 == 1)) {
   236  							t.Skip("skipping to save time")
   237  						}
   238  						t.Parallel()
   239  						testRedundancy(t, rLevel, encrypt, levels, chunkCnt, shardCnt, parityCnt)
   240  					})
   241  				}
   242  			}
   243  		})
   244  	}
   245  }
   246  
   247  func TestBzzFiles(t *testing.T) {
   248  	t.Parallel()
   249  
   250  	var (
   251  		fileUploadResource   = "/bzz"
   252  		fileDownloadResource = func(addr string) string { return "/bzz/" + addr }
   253  		simpleData           = []byte("this is a simple text")
   254  		storerMock           = mockstorer.New()
   255  		logger               = log.Noop
   256  		client, _, _, _      = newTestServer(t, testServerOptions{
   257  			Storer: storerMock,
   258  			Logger: logger,
   259  			Post:   mockpost.New(mockpost.WithAcceptAll()),
   260  		})
   261  	)
   262  
   263  	t.Run("tar-file-upload", func(t *testing.T) {
   264  		tr := tarFiles(t, []f{
   265  			{
   266  				data: []byte("robots text"),
   267  				name: "robots.txt",
   268  				dir:  "",
   269  				header: http.Header{
   270  					api.ContentTypeHeader: {"text/plain; charset=utf-8"},
   271  				},
   272  			},
   273  			{
   274  				data: []byte("image 1"),
   275  				name: "1.png",
   276  				dir:  "img",
   277  				header: http.Header{
   278  					api.ContentTypeHeader: {"image/png"},
   279  				},
   280  			},
   281  			{
   282  				data: []byte("image 2"),
   283  				name: "2.png",
   284  				dir:  "img",
   285  				header: http.Header{
   286  					api.ContentTypeHeader: {"image/png"},
   287  				},
   288  			},
   289  		})
   290  		address := swarm.MustParseHexAddress("f30c0aa7e9e2a0ef4c9b1b750ebfeaeb7c7c24da700bb089da19a46e3677824b")
   291  		rcvdHeader := jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource, http.StatusCreated,
   292  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   293  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   294  			jsonhttptest.WithRequestBody(tr),
   295  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
   296  			jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{
   297  				Reference: address,
   298  			}),
   299  			jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader),
   300  		)
   301  
   302  		isTagFoundInResponse(t, rcvdHeader, nil)
   303  
   304  		has, err := storerMock.ChunkStore().Has(context.Background(), address)
   305  		if err != nil {
   306  			t.Fatal(err)
   307  		}
   308  		if !has {
   309  			t.Fatal("storer check root chunk address: have none; want one")
   310  		}
   311  
   312  		refs, err := storerMock.Pins()
   313  		if err != nil {
   314  			t.Fatal("unable to get pinned references")
   315  		}
   316  		if have, want := len(refs), 0; have != want {
   317  			t.Fatalf("root pin count mismatch: have %d; want %d", have, want)
   318  		}
   319  	})
   320  
   321  	t.Run("tar-file-upload-with-pinning", func(t *testing.T) {
   322  		tr := tarFiles(t, []f{
   323  			{
   324  				data: []byte("robots text"),
   325  				name: "robots.txt",
   326  				dir:  "",
   327  				header: http.Header{
   328  					api.ContentTypeHeader: {"text/plain; charset=utf-8"},
   329  				},
   330  			},
   331  			{
   332  				data: []byte("image 1"),
   333  				name: "1.png",
   334  				dir:  "img",
   335  				header: http.Header{
   336  					api.ContentTypeHeader: {"image/png"},
   337  				},
   338  			},
   339  			{
   340  				data: []byte("image 2"),
   341  				name: "2.png",
   342  				dir:  "img",
   343  				header: http.Header{
   344  					api.ContentTypeHeader: {"image/png"},
   345  				},
   346  			},
   347  		})
   348  		reference := swarm.MustParseHexAddress("f30c0aa7e9e2a0ef4c9b1b750ebfeaeb7c7c24da700bb089da19a46e3677824b")
   349  		rcvdHeader := jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource, http.StatusCreated,
   350  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   351  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   352  			jsonhttptest.WithRequestHeader(api.SwarmPinHeader, "true"),
   353  			jsonhttptest.WithRequestBody(tr),
   354  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
   355  			jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{
   356  				Reference: reference,
   357  			}),
   358  			jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader),
   359  		)
   360  
   361  		isTagFoundInResponse(t, rcvdHeader, nil)
   362  
   363  		has, err := storerMock.ChunkStore().Has(context.Background(), reference)
   364  		if err != nil {
   365  			t.Fatal(err)
   366  		}
   367  		if !has {
   368  			t.Fatal("storer check root chunk reference: have none; want one")
   369  		}
   370  
   371  		refs, err := storerMock.Pins()
   372  		if err != nil {
   373  			t.Fatal(err)
   374  		}
   375  		if have, want := len(refs), 1; have != want {
   376  			t.Fatalf("root pin count mismatch: have %d; want %d", have, want)
   377  		}
   378  		if have, want := refs[0], reference; !have.Equal(want) {
   379  			t.Fatalf("root pin reference mismatch: have %q; want %q", have, want)
   380  		}
   381  	})
   382  
   383  	t.Run("encrypt-decrypt", func(t *testing.T) {
   384  		fileName := "my-pictures.jpeg"
   385  
   386  		var resp api.BzzUploadResponse
   387  		jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated,
   388  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   389  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   390  			jsonhttptest.WithRequestBody(bytes.NewReader(simpleData)),
   391  			jsonhttptest.WithRequestHeader(api.SwarmEncryptHeader, "True"),
   392  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "image/jpeg; charset=utf-8"),
   393  			jsonhttptest.WithUnmarshalJSONResponse(&resp),
   394  		)
   395  
   396  		jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(resp.Reference.String()), http.StatusOK,
   397  			jsonhttptest.WithExpectedContentLength(len(simpleData)),
   398  			jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "image/jpeg; charset=utf-8"),
   399  			jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)),
   400  			jsonhttptest.WithExpectedResponse(simpleData),
   401  		)
   402  	})
   403  
   404  	t.Run("redundancy", func(t *testing.T) {
   405  		fileName := "my-pictures.jpeg"
   406  
   407  		var resp api.BzzUploadResponse
   408  		jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated,
   409  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   410  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   411  			jsonhttptest.WithRequestBody(bytes.NewReader(simpleData)),
   412  			jsonhttptest.WithRequestHeader(api.SwarmEncryptHeader, "True"),
   413  			jsonhttptest.WithRequestHeader(api.SwarmRedundancyLevelHeader, "4"),
   414  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "image/jpeg; charset=utf-8"),
   415  			jsonhttptest.WithUnmarshalJSONResponse(&resp),
   416  		)
   417  
   418  		jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(resp.Reference.String()), http.StatusOK,
   419  			jsonhttptest.WithExpectedContentLength(len(simpleData)),
   420  			jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "image/jpeg; charset=utf-8"),
   421  			jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)),
   422  			jsonhttptest.WithExpectedResponse(simpleData),
   423  		)
   424  	})
   425  
   426  	t.Run("filter out filename path", func(t *testing.T) {
   427  		fileName := "my-pictures.jpeg"
   428  		fileNameWithPath := "../../" + fileName
   429  
   430  		var resp api.BzzUploadResponse
   431  
   432  		jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileNameWithPath, http.StatusCreated,
   433  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   434  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   435  			jsonhttptest.WithRequestBody(bytes.NewReader(simpleData)),
   436  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "image/jpeg; charset=utf-8"),
   437  			jsonhttptest.WithUnmarshalJSONResponse(&resp),
   438  		)
   439  
   440  		rootHash := resp.Reference.String()
   441  		jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(rootHash), http.StatusOK,
   442  			jsonhttptest.WithExpectedContentLength(len(simpleData)),
   443  			jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "image/jpeg; charset=utf-8"),
   444  			jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)),
   445  			jsonhttptest.WithExpectedResponse(simpleData),
   446  		)
   447  	})
   448  
   449  	t.Run("check-content-type-detection", func(t *testing.T) {
   450  		fileName := "my-pictures.jpeg"
   451  		rootHash := "4f9146b3813ccbd7ce45a18be23763d7e436ab7a3982ef39961c6f3cd4da1dcf"
   452  
   453  		jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated,
   454  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   455  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   456  			jsonhttptest.WithRequestBody(bytes.NewReader(simpleData)),
   457  			jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{
   458  				Reference: swarm.MustParseHexAddress(rootHash),
   459  			}),
   460  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "image/jpeg; charset=utf-8"),
   461  			jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader),
   462  		)
   463  
   464  		jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(rootHash), http.StatusOK,
   465  			jsonhttptest.WithExpectedResponse(simpleData),
   466  			jsonhttptest.WithExpectedContentLength(len(simpleData)),
   467  			jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "image/jpeg; charset=utf-8"),
   468  			jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)),
   469  		)
   470  	})
   471  
   472  	t.Run("upload-then-download-and-check-data", func(t *testing.T) {
   473  		fileName := "sample.html"
   474  		rootHash := "36e6c1bbdfee6ac21485d5f970479fd1df458d36df9ef4e8179708ed46da557f"
   475  		sampleHtml := `<!DOCTYPE html>
   476  		<html>
   477  		<body>
   478  
   479  		<h1>My First Heading</h1>
   480  
   481  		<p>My first paragraph.</p>
   482  
   483  		</body>
   484  		</html>`
   485  
   486  		jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated,
   487  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   488  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   489  			jsonhttptest.WithRequestBody(strings.NewReader(sampleHtml)),
   490  			jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{
   491  				Reference: swarm.MustParseHexAddress(rootHash),
   492  			}),
   493  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"),
   494  			jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader),
   495  			jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", rootHash)),
   496  		)
   497  
   498  		// try to fetch the same file and check the data
   499  		jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(rootHash), http.StatusOK,
   500  			jsonhttptest.WithExpectedResponse([]byte(sampleHtml)),
   501  			jsonhttptest.WithExpectedContentLength(len(sampleHtml)),
   502  			jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"),
   503  			jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)),
   504  		)
   505  	})
   506  
   507  	t.Run("upload-then-download-with-targets", func(t *testing.T) {
   508  		fileName := "simple_file.txt"
   509  		rootHash := "65148cd89b58e91616773f5acea433f7b5a6274f2259e25f4893a332b74a7e28"
   510  
   511  		jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated,
   512  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   513  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   514  			jsonhttptest.WithRequestBody(bytes.NewReader(simpleData)),
   515  			jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{
   516  				Reference: swarm.MustParseHexAddress(rootHash),
   517  			}),
   518  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"),
   519  			jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader),
   520  		)
   521  
   522  		t.Run("head", func(t *testing.T) {
   523  			rootHash := "65148cd89b58e91616773f5acea433f7b5a6274f2259e25f4893a332b74a7e28"
   524  
   525  			jsonhttptest.Request(t, client, http.MethodHead, fileDownloadResource(rootHash), http.StatusOK,
   526  				jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   527  				jsonhttptest.WithRequestBody(bytes.NewReader(simpleData)),
   528  				jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"),
   529  				jsonhttptest.WithExpectedContentLength(21),
   530  			)
   531  		})
   532  	})
   533  }
   534  
   535  // TestRangeRequests validates that all endpoints are serving content with
   536  // respect to HTTP Range headers.
   537  func TestBzzFilesRangeRequests(t *testing.T) {
   538  	t.Parallel()
   539  
   540  	data := []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus dignissim tincidunt orci id aliquam. Praesent eget turpis in lectus semper consectetur et ut nibh. Nam rhoncus, augue sit amet sollicitudin lacinia, turpis tortor molestie urna, at mattis sem sapien sit amet augue. In bibendum ex vel odio dignissim interdum. Quisque hendrerit sapien et porta condimentum. Vestibulum efficitur mauris tellus, eget vestibulum sapien vulputate ac. Proin et vulputate sapien. Duis tincidunt mauris vulputate porta venenatis. Sed dictum aliquet urna, sit amet fermentum velit pellentesque vitae. Nam sed nisi ultrices, volutpat quam et, malesuada sapien. Nunc gravida non orci at rhoncus. Sed vitae dui accumsan, venenatis lectus et, mattis tellus. Proin sed mauris eu mi congue lacinia.")
   541  
   542  	uploads := []struct {
   543  		name             string
   544  		uploadEndpoint   string
   545  		downloadEndpoint string
   546  		filepath         string
   547  		reader           io.Reader
   548  		contentType      string
   549  	}{
   550  		{
   551  			name:             "bytes",
   552  			uploadEndpoint:   "/bytes",
   553  			downloadEndpoint: "/bytes",
   554  			reader:           bytes.NewReader(data),
   555  			contentType:      "text/plain; charset=utf-8",
   556  		},
   557  		{
   558  			name:             "file",
   559  			uploadEndpoint:   "/bzz",
   560  			downloadEndpoint: "/bzz",
   561  			reader:           bytes.NewReader(data),
   562  			contentType:      "text/plain; charset=utf-8",
   563  		},
   564  		{
   565  			name:             "dir",
   566  			uploadEndpoint:   "/bzz",
   567  			downloadEndpoint: "/bzz",
   568  			filepath:         "ipsum/lorem.txt",
   569  			reader: tarFiles(t, []f{
   570  				{
   571  					data: data,
   572  					name: "lorem.txt",
   573  					dir:  "ipsum",
   574  					header: http.Header{
   575  						api.ContentTypeHeader: {"text/plain; charset=utf-8"},
   576  					},
   577  				},
   578  			}),
   579  			contentType: api.ContentTypeTar,
   580  		},
   581  	}
   582  
   583  	ranges := []struct {
   584  		name   string
   585  		ranges [][2]int
   586  	}{
   587  		{
   588  			name:   "all",
   589  			ranges: [][2]int{{0, len(data)}},
   590  		},
   591  		{
   592  			name:   "all without end",
   593  			ranges: [][2]int{{0, -1}},
   594  		},
   595  		{
   596  			name:   "all without start",
   597  			ranges: [][2]int{{-1, len(data)}},
   598  		},
   599  		{
   600  			name:   "head",
   601  			ranges: [][2]int{{0, 50}},
   602  		},
   603  		{
   604  			name:   "tail",
   605  			ranges: [][2]int{{250, len(data)}},
   606  		},
   607  		{
   608  			name:   "middle",
   609  			ranges: [][2]int{{10, 15}},
   610  		},
   611  		{
   612  			name:   "multiple",
   613  			ranges: [][2]int{{10, 15}, {100, 125}},
   614  		},
   615  		{
   616  			name:   "even more multiple parts",
   617  			ranges: [][2]int{{10, 15}, {100, 125}, {250, 252}, {261, 270}, {270, 280}},
   618  		},
   619  	}
   620  
   621  	for _, upload := range uploads {
   622  		upload := upload
   623  		t.Run(upload.name, func(t *testing.T) {
   624  			t.Parallel()
   625  
   626  			logger := log.Noop
   627  			client, _, _, _ := newTestServer(t, testServerOptions{
   628  				Storer: mockstorer.New(),
   629  				Logger: logger,
   630  				Post:   mockpost.New(mockpost.WithAcceptAll()),
   631  			})
   632  
   633  			var resp api.BzzUploadResponse
   634  
   635  			testOpts := []jsonhttptest.Option{
   636  				jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   637  				jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   638  				jsonhttptest.WithRequestBody(upload.reader),
   639  				jsonhttptest.WithRequestHeader(api.ContentTypeHeader, upload.contentType),
   640  				jsonhttptest.WithUnmarshalJSONResponse(&resp),
   641  				jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader),
   642  			}
   643  			if upload.name == "dir" {
   644  				testOpts = append(testOpts, jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"))
   645  			}
   646  
   647  			jsonhttptest.Request(t, client, http.MethodPost, upload.uploadEndpoint, http.StatusCreated,
   648  				testOpts...,
   649  			)
   650  
   651  			var downloadPath string
   652  			if upload.downloadEndpoint != "/bytes" {
   653  				downloadPath = upload.downloadEndpoint + "/" + resp.Reference.String() + "/" + upload.filepath
   654  			} else {
   655  				downloadPath = upload.downloadEndpoint + "/" + resp.Reference.String()
   656  			}
   657  
   658  			for _, tc := range ranges {
   659  				t.Run(tc.name, func(t *testing.T) {
   660  					rangeHeader, want := createRangeHeader(data, tc.ranges)
   661  
   662  					var body []byte
   663  					respHeaders := jsonhttptest.Request(t, client, http.MethodGet,
   664  						downloadPath,
   665  						http.StatusPartialContent,
   666  						jsonhttptest.WithRequestHeader(api.RangeHeader, rangeHeader),
   667  						jsonhttptest.WithPutResponseBody(&body),
   668  					)
   669  
   670  					got := parseRangeParts(t, respHeaders.Get(api.ContentTypeHeader), body)
   671  
   672  					if len(got) != len(want) {
   673  						t.Fatalf("got %v parts, want %v parts", len(got), len(want))
   674  					}
   675  					for i := 0; i < len(want); i++ {
   676  						if !bytes.Equal(got[i], want[i]) {
   677  							t.Errorf("part %v: got %q, want %q", i, string(got[i]), string(want[i]))
   678  						}
   679  					}
   680  				})
   681  			}
   682  		})
   683  	}
   684  }
   685  
   686  func createRangeHeader(data interface{}, ranges [][2]int) (header string, parts [][]byte) {
   687  	getLen := func() int {
   688  		switch data := data.(type) {
   689  		case []byte:
   690  			return len(data)
   691  		case interface{ Size() int }:
   692  			return data.Size()
   693  		default:
   694  			panic("unknown data type")
   695  		}
   696  	}
   697  	getRange := func(start, end int) []byte {
   698  		switch data := data.(type) {
   699  		case []byte:
   700  			return data[start:end]
   701  		case io.ReadSeeker:
   702  			buf := make([]byte, end-start)
   703  			_, err := data.Seek(int64(start), io.SeekStart)
   704  			if err != nil {
   705  				panic(err)
   706  			}
   707  			_, err = io.ReadFull(data, buf)
   708  			if err != nil {
   709  				panic(err)
   710  			}
   711  			return buf
   712  		default:
   713  			panic("unknown data type")
   714  		}
   715  	}
   716  
   717  	rangeStrs := make([]string, len(ranges))
   718  	for i, r := range ranges {
   719  		start, end := r[0], r[1]
   720  		switch {
   721  		case start < 0:
   722  			// Range: <unit>=-<suffix-length>, the parameter is length
   723  			rangeStrs[i] = "-" + strconv.Itoa(end)
   724  			start = 0
   725  		case r[1] < 0:
   726  			// Range: <unit>=<range-start>-
   727  			rangeStrs[i] = strconv.Itoa(start) + "-"
   728  			end = getLen()
   729  		default:
   730  			// Range: <unit>=<range-start>-<range-end>, end is inclusive
   731  			rangeStrs[i] = fmt.Sprintf("%v-%v", start, end-1)
   732  		}
   733  		parts = append(parts, getRange(start, end))
   734  	}
   735  	header = "bytes=" + strings.Join(rangeStrs, ", ") // nolint:staticcheck
   736  	return header, parts
   737  }
   738  
   739  func parseRangeParts(t *testing.T, contentType string, body []byte) (parts [][]byte) {
   740  	t.Helper()
   741  
   742  	mimetype, params, _ := mime.ParseMediaType(contentType)
   743  	if mimetype != "multipart/byteranges" {
   744  		parts = append(parts, body)
   745  		return
   746  	}
   747  	mr := multipart.NewReader(bytes.NewReader(body), params["boundary"])
   748  	for part, err := mr.NextPart(); err == nil; part, err = mr.NextPart() {
   749  		value, err := io.ReadAll(part)
   750  		if err != nil {
   751  			t.Fatal(err)
   752  		}
   753  		parts = append(parts, value)
   754  	}
   755  	return parts
   756  }
   757  
   758  func TestFeedIndirection(t *testing.T) {
   759  	t.Parallel()
   760  
   761  	// first, "upload" some content for the update
   762  	var (
   763  		updateData      = []byte("<h1>Swarm Feeds Hello World!</h1>")
   764  		logger          = log.Noop
   765  		storer          = mockstorer.New()
   766  		client, _, _, _ = newTestServer(t, testServerOptions{
   767  			Storer: storer,
   768  			Logger: logger,
   769  			Post:   mockpost.New(mockpost.WithAcceptAll()),
   770  		})
   771  	)
   772  	// tar all the test case files
   773  	tarReader := tarFiles(t, []f{
   774  		{
   775  			data:     updateData,
   776  			name:     "index.html",
   777  			dir:      "",
   778  			filePath: "./index.html",
   779  		},
   780  	})
   781  
   782  	var resp api.BzzUploadResponse
   783  
   784  	options := []jsonhttptest.Option{
   785  		jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   786  		jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   787  		jsonhttptest.WithRequestBody(tarReader),
   788  		jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
   789  		jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"),
   790  		jsonhttptest.WithUnmarshalJSONResponse(&resp),
   791  		jsonhttptest.WithRequestHeader(api.SwarmIndexDocumentHeader, "index.html"),
   792  	}
   793  
   794  	// verify directory tar upload response
   795  	jsonhttptest.Request(t, client, http.MethodPost, "/bzz", http.StatusCreated, options...)
   796  
   797  	if resp.Reference.String() == "" {
   798  		t.Fatalf("expected file reference, did not got any")
   799  	}
   800  
   801  	// now use the "content" to mock the feed lookup
   802  	// also, use the mocked mantaray chunks that unmarshal
   803  	// into a real manifest with the mocked feed values when
   804  	// called from the bzz endpoint. then call the bzz endpoint with
   805  	// the pregenerated feed root manifest hash
   806  
   807  	feedUpdate := toChunk(t, 121212, resp.Reference.Bytes())
   808  
   809  	var (
   810  		look                = newMockLookup(-1, 0, feedUpdate, nil, &id{}, nil)
   811  		factory             = newMockFactory(look)
   812  		bzzDownloadResource = func(addr, path string) string { return "/bzz/" + addr + "/" + path }
   813  		ctx                 = context.Background()
   814  	)
   815  	client, _, _, _ = newTestServer(t, testServerOptions{
   816  		Storer: storer,
   817  		Logger: logger,
   818  		Feeds:  factory,
   819  	})
   820  	err := storer.Cache().Put(ctx, feedUpdate)
   821  	if err != nil {
   822  		t.Fatal(err)
   823  	}
   824  	m, err := manifest.NewDefaultManifest(
   825  		loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false, 0)),
   826  		false,
   827  	)
   828  	if err != nil {
   829  		t.Fatal(err)
   830  	}
   831  	emptyAddr := make([]byte, 32)
   832  	err = m.Add(ctx, manifest.RootPath, manifest.NewEntry(swarm.NewAddress(emptyAddr), map[string]string{
   833  		api.FeedMetadataEntryOwner: "8d3766440f0d7b949a5e32995d09619a7f86e632",
   834  		api.FeedMetadataEntryTopic: "abcc",
   835  		api.FeedMetadataEntryType:  "epoch",
   836  	}))
   837  	if err != nil {
   838  		t.Fatal(err)
   839  	}
   840  	manifRef, err := m.Store(ctx)
   841  	if err != nil {
   842  		t.Fatal(err)
   843  	}
   844  
   845  	jsonhttptest.Request(t, client, http.MethodGet, bzzDownloadResource(manifRef.String(), ""), http.StatusOK,
   846  		jsonhttptest.WithExpectedResponse(updateData),
   847  		jsonhttptest.WithExpectedContentLength(len(updateData)),
   848  	)
   849  }
   850  
   851  func Test_bzzDownloadHandler_invalidInputs(t *testing.T) {
   852  	t.Parallel()
   853  
   854  	client, _, _, _ := newTestServer(t, testServerOptions{})
   855  
   856  	tests := []struct {
   857  		name    string
   858  		address string
   859  		want    jsonhttp.StatusResponse
   860  	}{{
   861  		name:    "address - odd hex string",
   862  		address: "123",
   863  		want: jsonhttp.StatusResponse{
   864  			Code:    http.StatusBadRequest,
   865  			Message: "invalid path params",
   866  			Reasons: []jsonhttp.Reason{
   867  				{
   868  					Field: "address",
   869  					Error: api.ErrHexLength.Error(),
   870  				},
   871  			},
   872  		},
   873  	}, {
   874  		name:    "address - invalid hex character",
   875  		address: "123G",
   876  		want: jsonhttp.StatusResponse{
   877  			Code:    http.StatusBadRequest,
   878  			Message: "invalid path params",
   879  			Reasons: []jsonhttp.Reason{
   880  				{
   881  					Field: "address",
   882  					Error: api.HexInvalidByteError('G').Error(),
   883  				},
   884  			},
   885  		},
   886  	}}
   887  
   888  	for _, tc := range tests {
   889  		tc := tc
   890  		t.Run(tc.name, func(t *testing.T) {
   891  			t.Parallel()
   892  
   893  			jsonhttptest.Request(t, client, http.MethodGet, fmt.Sprintf("/bzz/%s/abc", tc.address), tc.want.Code,
   894  				jsonhttptest.WithExpectedJSONResponse(tc.want),
   895  			)
   896  		})
   897  	}
   898  }
   899  
   900  func TestInvalidBzzParams(t *testing.T) {
   901  	t.Parallel()
   902  
   903  	var (
   904  		fileUploadResource = "/bzz"
   905  		storerMock         = mockstorer.New()
   906  		logger             = log.Noop
   907  		existsFn           = func(id []byte) (bool, error) {
   908  			return false, errors.New("error")
   909  		}
   910  	)
   911  
   912  	t.Run("batch unusable", func(t *testing.T) {
   913  		t.Parallel()
   914  
   915  		tr := tarFiles(t, []f{
   916  			{
   917  				data: []byte("robots text"),
   918  				name: "robots.txt",
   919  				dir:  "",
   920  				header: http.Header{
   921  					api.ContentTypeHeader: {"text/plain; charset=utf-8"},
   922  				},
   923  			},
   924  		})
   925  		clientBatchUnusable, _, _, _ := newTestServer(t, testServerOptions{
   926  			Storer:     storerMock,
   927  			Logger:     logger,
   928  			Post:       mockpost.New(mockpost.WithAcceptAll()),
   929  			BatchStore: mockbatchstore.New(),
   930  		})
   931  		jsonhttptest.Request(t, clientBatchUnusable, http.MethodPost, fileUploadResource, http.StatusUnprocessableEntity,
   932  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   933  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   934  			jsonhttptest.WithRequestBody(tr),
   935  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
   936  		)
   937  
   938  	})
   939  
   940  	t.Run("batch exists", func(t *testing.T) {
   941  		t.Parallel()
   942  
   943  		tr := tarFiles(t, []f{
   944  			{
   945  				data: []byte("robots text"),
   946  				name: "robots.txt",
   947  				dir:  "",
   948  				header: http.Header{
   949  					api.ContentTypeHeader: {"text/plain; charset=utf-8"},
   950  				},
   951  			},
   952  		})
   953  		clientBatchExists, _, _, _ := newTestServer(t, testServerOptions{
   954  			Storer:     storerMock,
   955  			Logger:     logger,
   956  			Post:       mockpost.New(mockpost.WithAcceptAll()),
   957  			BatchStore: mockbatchstore.New(mockbatchstore.WithExistsFunc(existsFn)),
   958  		})
   959  		jsonhttptest.Request(t, clientBatchExists, http.MethodPost, fileUploadResource, http.StatusBadRequest,
   960  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   961  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   962  			jsonhttptest.WithRequestBody(tr),
   963  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
   964  		)
   965  
   966  	})
   967  
   968  	t.Run("batch not found", func(t *testing.T) {
   969  		t.Parallel()
   970  
   971  		tr := tarFiles(t, []f{
   972  			{
   973  				data: []byte("robots text"),
   974  				name: "robots.txt",
   975  				dir:  "",
   976  				header: http.Header{
   977  					api.ContentTypeHeader: {"text/plain; charset=utf-8"},
   978  				},
   979  			},
   980  		})
   981  		clientBatchExists, _, _, _ := newTestServer(t, testServerOptions{
   982  			Storer: storerMock,
   983  			Logger: logger,
   984  			Post:   mockpost.New(),
   985  		})
   986  		jsonhttptest.Request(t, clientBatchExists, http.MethodPost, fileUploadResource, http.StatusNotFound,
   987  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   988  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   989  			jsonhttptest.WithRequestBody(tr),
   990  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
   991  		)
   992  	})
   993  
   994  	t.Run("upload, invalid tag", func(t *testing.T) {
   995  		t.Parallel()
   996  
   997  		tr := tarFiles(t, []f{
   998  			{
   999  				data: []byte("robots text"),
  1000  				name: "robots.txt",
  1001  				dir:  "",
  1002  				header: http.Header{
  1003  					api.ContentTypeHeader: {"text/plain; charset=utf-8"},
  1004  				},
  1005  			},
  1006  		})
  1007  		clientInvalidTag, _, _, _ := newTestServer(t, testServerOptions{
  1008  			Storer: storerMock,
  1009  			Logger: logger,
  1010  			Post:   mockpost.New(mockpost.WithAcceptAll()),
  1011  		})
  1012  
  1013  		jsonhttptest.Request(t, clientInvalidTag, http.MethodPost, fileUploadResource, http.StatusBadRequest,
  1014  			jsonhttptest.WithRequestHeader(api.SwarmTagHeader, "tag"),
  1015  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
  1016  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
  1017  			jsonhttptest.WithRequestBody(tr),
  1018  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar))
  1019  	})
  1020  
  1021  	t.Run("upload, tag not found", func(t *testing.T) {
  1022  		t.Parallel()
  1023  
  1024  		tr := tarFiles(t, []f{
  1025  			{
  1026  				data: []byte("robots text"),
  1027  				name: "robots.txt",
  1028  				dir:  "",
  1029  				header: http.Header{
  1030  					api.ContentTypeHeader: {"text/plain; charset=utf-8"},
  1031  				},
  1032  			},
  1033  		})
  1034  		clientTagExists, _, _, _ := newTestServer(t, testServerOptions{
  1035  			Storer: storerMock,
  1036  			Logger: logger,
  1037  			Post:   mockpost.New(mockpost.WithAcceptAll()),
  1038  		})
  1039  
  1040  		jsonhttptest.Request(t, clientTagExists, http.MethodPost, fileUploadResource, http.StatusNotFound,
  1041  			jsonhttptest.WithRequestHeader(api.SwarmTagHeader, strconv.FormatUint(10000, 10)),
  1042  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
  1043  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
  1044  			jsonhttptest.WithRequestBody(tr),
  1045  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar))
  1046  	})
  1047  
  1048  	t.Run("address not found", func(t *testing.T) {
  1049  		t.Parallel()
  1050  
  1051  		client, _, _, _ := newTestServer(t, testServerOptions{
  1052  			Storer: storerMock,
  1053  			Logger: logger,
  1054  			Post:   mockpost.New(mockpost.WithAcceptAll()),
  1055  		})
  1056  
  1057  		address := "f30c0aa7e9e2a0ef4c9b1b750ebfeaeb7c7c24da700bb089da19a46e3677824b"
  1058  		jsonhttptest.Request(t, client, http.MethodGet, fmt.Sprintf("/bzz/%s/", address), http.StatusNotFound)
  1059  	})
  1060  
  1061  }
  1062  
  1063  // TestDirectUploadBzz tests that the direct upload endpoint give correct error message in dev mode
  1064  func TestDirectUploadBzz(t *testing.T) {
  1065  	t.Parallel()
  1066  
  1067  	var (
  1068  		fileUploadResource = "/bzz"
  1069  		storerMock         = mockstorer.New()
  1070  		logger             = log.Noop
  1071  	)
  1072  
  1073  	tr := tarFiles(t, []f{
  1074  		{
  1075  			data: []byte("robots text"),
  1076  			name: "robots.txt",
  1077  			dir:  "",
  1078  			header: http.Header{
  1079  				api.ContentTypeHeader: {"text/plain; charset=utf-8"},
  1080  			},
  1081  		},
  1082  	})
  1083  	clientBatchUnusable, _, _, _ := newTestServer(t, testServerOptions{
  1084  		Storer:     storerMock,
  1085  		Logger:     logger,
  1086  		Post:       mockpost.New(mockpost.WithAcceptAll()),
  1087  		BatchStore: mockbatchstore.New(),
  1088  		BeeMode:    api.DevMode,
  1089  	})
  1090  	jsonhttptest.Request(t, clientBatchUnusable, http.MethodPost, fileUploadResource, http.StatusBadRequest,
  1091  		jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "false"),
  1092  		jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
  1093  		jsonhttptest.WithRequestBody(tr),
  1094  		jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
  1095  		jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
  1096  			Message: api.ErrUnsupportedDevNodeOperation.Error(),
  1097  			Code:    http.StatusBadRequest,
  1098  		}),
  1099  	)
  1100  }