github.com/ethersphere/bee/v2@v2.2.0/pkg/api/tag_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  	"fmt"
     9  	"net/http"
    10  	"sort"
    11  	"strconv"
    12  	"testing"
    13  
    14  	mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock"
    15  	mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock"
    16  	"github.com/ethersphere/bee/v2/pkg/swarm"
    17  	"github.com/google/go-cmp/cmp"
    18  
    19  	"github.com/ethersphere/bee/v2/pkg/api"
    20  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    21  	"github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest"
    22  )
    23  
    24  func tagsWithIdResource(id uint64) string { return fmt.Sprintf("/tags/%d", id) }
    25  
    26  // nolint:paralleltest
    27  func TestTags(t *testing.T) {
    28  
    29  	var (
    30  		tagsResource    = "/tags"
    31  		storerMock      = mockstorer.New()
    32  		client, _, _, _ = newTestServer(t, testServerOptions{
    33  			Storer: storerMock,
    34  			Post:   mockpost.New(mockpost.WithAcceptAll()),
    35  		})
    36  	)
    37  
    38  	// list tags without anything pinned
    39  	t.Run("list tags zero", func(t *testing.T) {
    40  		jsonhttptest.Request(t, client, http.MethodGet, tagsResource, http.StatusOK,
    41  			jsonhttptest.WithExpectedJSONResponse(api.ListTagsResponse{
    42  				Tags: []api.TagResponse{},
    43  			}),
    44  		)
    45  	})
    46  
    47  	t.Run("create tag", func(t *testing.T) {
    48  		tr := api.TagResponse{}
    49  		jsonhttptest.Request(t, client, http.MethodPost, tagsResource, http.StatusCreated,
    50  			jsonhttptest.WithJSONRequestBody(api.TagRequest{}),
    51  			jsonhttptest.WithUnmarshalJSONResponse(&tr),
    52  		)
    53  	})
    54  
    55  	t.Run("get non-existent tag", func(t *testing.T) {
    56  		jsonhttptest.Request(t, client, http.MethodGet, tagsWithIdResource(uint64(333)), http.StatusNotFound,
    57  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
    58  				Message: "tag not present",
    59  				Code:    http.StatusNotFound,
    60  			}),
    61  		)
    62  	})
    63  
    64  	t.Run("list tags", func(t *testing.T) {
    65  		// list all current tags
    66  		var resp api.ListTagsResponse
    67  		jsonhttptest.Request(t, client, http.MethodGet, tagsResource, http.StatusOK,
    68  			jsonhttptest.WithUnmarshalJSONResponse(&resp),
    69  		)
    70  
    71  		// create 2 new tags
    72  		tRes1 := api.TagResponse{}
    73  		jsonhttptest.Request(t, client, http.MethodPost, tagsResource, http.StatusCreated,
    74  			jsonhttptest.WithJSONRequestBody(api.TagRequest{}),
    75  			jsonhttptest.WithUnmarshalJSONResponse(&tRes1),
    76  		)
    77  
    78  		tRes2 := api.TagResponse{}
    79  		jsonhttptest.Request(t, client, http.MethodPost, tagsResource, http.StatusCreated,
    80  			jsonhttptest.WithJSONRequestBody(api.TagRequest{}),
    81  			jsonhttptest.WithUnmarshalJSONResponse(&tRes2),
    82  		)
    83  
    84  		resp.Tags = append(resp.Tags, tRes1, tRes2)
    85  
    86  		// check if listing returns expected tags
    87  		var got api.ListTagsResponse
    88  		jsonhttptest.Request(t, client, http.MethodGet, tagsResource, http.StatusOK,
    89  			jsonhttptest.WithUnmarshalJSONResponse(&got),
    90  		)
    91  
    92  		sort.Slice(resp.Tags, func(i, j int) bool { return resp.Tags[i].Uid < resp.Tags[j].Uid })
    93  		sort.Slice(got.Tags, func(i, j int) bool { return got.Tags[i].Uid < got.Tags[j].Uid })
    94  
    95  		if diff := cmp.Diff(resp.Tags, got.Tags); diff != "" {
    96  			t.Fatalf("unexpected tags (-want +have):\n%s", diff)
    97  		}
    98  	})
    99  
   100  	t.Run("delete non-existent tag", func(t *testing.T) {
   101  		// try to delete non-existent tag
   102  		jsonhttptest.Request(t, client, http.MethodDelete, tagsWithIdResource(uint64(333)), http.StatusNotFound,
   103  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
   104  				Message: "tag not present",
   105  				Code:    http.StatusNotFound,
   106  			}),
   107  		)
   108  	})
   109  
   110  	t.Run("delete tag", func(t *testing.T) {
   111  		// create a tag through API
   112  		tRes := api.TagResponse{}
   113  		jsonhttptest.Request(t, client, http.MethodPost, tagsResource, http.StatusCreated,
   114  			jsonhttptest.WithJSONRequestBody(api.TagRequest{}),
   115  			jsonhttptest.WithUnmarshalJSONResponse(&tRes),
   116  		)
   117  
   118  		// check tag existence
   119  		jsonhttptest.Request(t, client, http.MethodGet, tagsWithIdResource(tRes.Uid), http.StatusOK)
   120  
   121  		// delete tag through API
   122  		jsonhttptest.Request(t, client, http.MethodDelete, tagsWithIdResource(tRes.Uid), http.StatusNoContent,
   123  			jsonhttptest.WithNoResponseBody(),
   124  		)
   125  
   126  		// try to get tag
   127  		jsonhttptest.Request(t, client, http.MethodGet, tagsWithIdResource(tRes.Uid), http.StatusNotFound,
   128  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
   129  				Message: "tag not present",
   130  				Code:    http.StatusNotFound,
   131  			}),
   132  		)
   133  	})
   134  
   135  	t.Run("done split non-existent tag", func(t *testing.T) {
   136  		// non-existent tag
   137  		jsonhttptest.Request(t, client, http.MethodPatch, tagsWithIdResource(uint64(333)), http.StatusNotFound,
   138  			jsonhttptest.WithJSONRequestBody(api.TagResponse{}),
   139  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
   140  				Message: "tag not present",
   141  				Code:    http.StatusNotFound,
   142  			}),
   143  		)
   144  	})
   145  
   146  	t.Run("done split", func(t *testing.T) {
   147  		// create a tag through API
   148  		tRes := api.TagResponse{}
   149  		jsonhttptest.Request(t, client, http.MethodPost, tagsResource, http.StatusCreated,
   150  			jsonhttptest.WithUnmarshalJSONResponse(&tRes),
   151  		)
   152  		tagId := tRes.Uid
   153  
   154  		// generate address to be supplied to the done split
   155  		addr := swarm.RandAddress(t)
   156  
   157  		// call done split
   158  		jsonhttptest.Request(t, client, http.MethodPatch, tagsWithIdResource(tagId), http.StatusOK,
   159  			jsonhttptest.WithJSONRequestBody(api.TagRequest{
   160  				Address: addr,
   161  			}),
   162  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
   163  				Message: "ok",
   164  				Code:    http.StatusOK,
   165  			}),
   166  		)
   167  
   168  		jsonhttptest.Request(t, client, http.MethodGet, tagsWithIdResource(tagId), http.StatusOK,
   169  			jsonhttptest.WithUnmarshalJSONResponse(&tRes),
   170  		)
   171  
   172  		if !tRes.Address.Equal(addr) {
   173  			t.Fatalf("invalid session reference got %s want %s", tRes.Address, addr)
   174  		}
   175  
   176  		// try different address value
   177  		addr = swarm.RandAddress(t)
   178  
   179  		// call done split
   180  		jsonhttptest.Request(t, client, http.MethodPatch, tagsWithIdResource(tagId), http.StatusOK,
   181  			jsonhttptest.WithJSONRequestBody(api.TagRequest{
   182  				Address: addr,
   183  			}),
   184  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
   185  				Message: "ok",
   186  				Code:    http.StatusOK,
   187  			}),
   188  		)
   189  
   190  		jsonhttptest.Request(t, client, http.MethodGet, tagsWithIdResource(tagId), http.StatusOK,
   191  			jsonhttptest.WithUnmarshalJSONResponse(&tRes),
   192  		)
   193  
   194  		if !tRes.Address.Equal(addr) {
   195  			t.Fatalf("invalid session reference got %s want %s", tRes.Address, addr)
   196  		}
   197  	})
   198  }
   199  
   200  func TestTagsHandlersInvalidInputs(t *testing.T) {
   201  	t.Parallel()
   202  
   203  	client, _, _, _ := newTestServer(t, testServerOptions{})
   204  
   205  	tests := []struct {
   206  		name  string
   207  		tagID string
   208  		want  jsonhttp.StatusResponse
   209  	}{{
   210  		name:  "id - invalid value",
   211  		tagID: "a",
   212  		want: jsonhttp.StatusResponse{
   213  			Code:    http.StatusBadRequest,
   214  			Message: "invalid path params",
   215  			Reasons: []jsonhttp.Reason{
   216  				{
   217  					Field: "id",
   218  					Error: strconv.ErrSyntax.Error(),
   219  				},
   220  			},
   221  		},
   222  	}}
   223  
   224  	for _, method := range []string{http.MethodGet, http.MethodDelete, http.MethodPatch} {
   225  		method := method
   226  		for _, tc := range tests {
   227  			tc := tc
   228  			t.Run(method+" "+tc.name, func(t *testing.T) {
   229  				t.Parallel()
   230  
   231  				jsonhttptest.Request(t, client, method, "/tags/"+tc.tagID, tc.want.Code,
   232  					jsonhttptest.WithExpectedJSONResponse(tc.want),
   233  				)
   234  			})
   235  		}
   236  	}
   237  }
   238  
   239  // isTagFoundInResponse verifies that the tag id is found in the supplied HTTP headers
   240  // if an API tag response is supplied, it also verifies that it contains an id which matches the headers
   241  func isTagFoundInResponse(t *testing.T, headers http.Header, tr *api.TagResponse) uint64 {
   242  	t.Helper()
   243  
   244  	idStr := headers.Get(api.SwarmTagHeader)
   245  	if idStr == "" {
   246  		t.Fatalf("could not find tag id header in chunk upload response")
   247  	}
   248  	nId, err := strconv.Atoi(idStr)
   249  	id := uint64(nId)
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  	if tr != nil {
   254  		if id != tr.Uid {
   255  			t.Fatalf("expected created tag id to be %d, but got %d when uploading chunk", tr.Uid, id)
   256  		}
   257  	}
   258  	return id
   259  }