Skip to content

Commit 7777736

Browse files
authored
fix(getobjecttree): getobjecttree should work even allocation is empty (#605)
1 parent 92b218c commit 7777736

File tree

7 files changed

+307
-79
lines changed

7 files changed

+307
-79
lines changed

code/go/0chain.net/blobbercore/handler/grpc_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func (b *blobberGRPCService) GetObjectTree(ctx context.Context, req *blobbergrpc
141141
"path": {req.Path},
142142
}
143143

144-
resp, err := ObjectTreeHandler(ctx, r)
144+
resp, _, err := ObjectTreeHandler(ctx, r)
145145
if err != nil {
146146
return nil, err
147147
}

code/go/0chain.net/blobbercore/handler/handler.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func SetupHandlers(r *mux.Router) {
6464
r.HandleFunc("/v1/file/list/{allocation}", common.ToJSONResponse(WithReadOnlyConnection(ListHandler))).Methods(http.MethodGet, http.MethodOptions)
6565
r.HandleFunc("/v1/file/objectpath/{allocation}", common.ToJSONResponse(WithReadOnlyConnection(ObjectPathHandler)))
6666
r.HandleFunc("/v1/file/referencepath/{allocation}", common.ToJSONResponse(WithReadOnlyConnection(ReferencePathHandler)))
67-
r.HandleFunc("/v1/file/objecttree/{allocation}", common.ToJSONResponse(WithReadOnlyConnection(ObjectTreeHandler)))
67+
r.HandleFunc("/v1/file/objecttree/{allocation}", common.ToStatusCode(WithStatusReadOnlyConnection(ObjectTreeHandler))).Methods(http.MethodGet, http.MethodOptions)
6868
r.HandleFunc("/v1/file/refs/{allocation}", common.ToJSONResponse(WithReadOnlyConnection(RefsHandler))).Methods(http.MethodGet, http.MethodOptions)
6969
//admin related
7070
r.HandleFunc("/_debug", common.ToJSONResponse(DumpGoRoutines))
@@ -153,6 +153,17 @@ func WithStatusConnection(handler common.StatusCodeResponderF) common.StatusCode
153153
}
154154
}
155155

156+
func WithStatusReadOnlyConnection(handler common.StatusCodeResponderF) common.StatusCodeResponderF {
157+
return func(ctx context.Context, r *http.Request) (interface{}, int, error) {
158+
ctx = GetMetaDataStore().CreateTransaction(ctx)
159+
resp, statusCode, err := handler(ctx, r)
160+
defer func() {
161+
GetMetaDataStore().GetTransaction(ctx).Rollback()
162+
}()
163+
return resp, statusCode, err
164+
}
165+
}
166+
156167
func setupHandlerContext(ctx context.Context, r *http.Request) context.Context {
157168
var vars = mux.Vars(r)
158169
ctx = context.WithValue(ctx, constants.ContextKeyClient,
@@ -312,15 +323,18 @@ func ObjectPathHandler(ctx context.Context, r *http.Request) (interface{}, error
312323
return response, nil
313324
}
314325

315-
func ObjectTreeHandler(ctx context.Context, r *http.Request) (interface{}, error) {
326+
func ObjectTreeHandler(ctx context.Context, r *http.Request) (interface{}, int, error) {
316327
ctx = setupHandlerContext(ctx, r)
317328

318329
response, err := storageHandler.GetObjectTree(ctx, r)
319330
if err != nil {
320-
return nil, err
331+
if errors.Is(common.ErrNotFound, err) {
332+
return response, http.StatusNoContent, nil
333+
}
334+
return nil, http.StatusBadRequest, err
321335
}
322336

323-
return response, nil
337+
return response, http.StatusOK, nil
324338
}
325339

326340
func RefsHandler(ctx context.Context, r *http.Request) (interface{}, error) {
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
package handler
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"os"
8+
"regexp"
9+
"testing"
10+
"time"
11+
12+
"github.com/0chain/gosdk/core/zcncrypto"
13+
"github.com/0chain/gosdk/zboxcore/client"
14+
"github.com/DATA-DOG/go-sqlmock"
15+
"github.com/gorilla/mux"
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/require"
18+
19+
"github.com/0chain/blobber/code/go/0chain.net/blobbercore/allocation"
20+
"github.com/0chain/blobber/code/go/0chain.net/blobbercore/datastore"
21+
"github.com/0chain/blobber/code/go/0chain.net/core/common"
22+
"github.com/0chain/blobber/code/go/0chain.net/core/encryption"
23+
)
24+
25+
func setupObjectTreeHandlers() (*mux.Router, map[string]string) {
26+
router := mux.NewRouter()
27+
28+
otPath := "/v1/file/objecttree/{allocation}"
29+
otName := "Object_Tree"
30+
router.HandleFunc(otPath, common.UserRateLimit(
31+
common.ToStatusCode(
32+
WithStatusReadOnlyConnection(ObjectTreeHandler),
33+
),
34+
),
35+
).Name(otName)
36+
37+
return router,
38+
map[string]string{
39+
otPath: otName,
40+
}
41+
}
42+
43+
func TestHandlers_ObjectTree(t *testing.T) {
44+
setup(t)
45+
46+
clientJson := `{"client_id":"2f34516ed8c567089b7b5572b12950db34a62a07e16770da14b15b170d0d60a9","client_key":"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083","keys":[{"public_key":"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083","private_key":"9fef6ff5edc39a79c1d8e5eb7ca7e5ac14d34615ee49e6d8ca12ecec136f5907"}],"mnemonics":"expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe","version":"1.0","date_created":"2021-05-30 17:45:06.492093 +0545 +0545 m=+0.139083805"}`
47+
guestClientJson := `{"client_id":"213297e22c8282ff85d1d5c99f4967636fe68f842c1351b24bd497246cbd26d9","client_key":"7710b547897e0bddf93a28903875b244db4d320e4170172b19a5d51280c73522e9bb381b184fa3d24d6e1464882bf7f89d24ac4e8d05616d55eb857a6e235383","keys":[{"public_key":"7710b547897e0bddf93a28903875b244db4d320e4170172b19a5d51280c73522e9bb381b184fa3d24d6e1464882bf7f89d24ac4e8d05616d55eb857a6e235383","private_key":"19ca446f814dcd56e28e11d4147f73590a07c7f1a9a6012087808a8602024a08"}],"mnemonics":"crazy dutch object arrest jump fragile oak amateur taxi trigger gap aspect marriage hat slice wool island spike unlock alter include easily say ramp","version":"1.0","date_created":"2022-01-26T07:26:41+05:45"}`
48+
49+
require.NoError(t, client.PopulateClients([]string{clientJson, guestClientJson}, "bls0chain"))
50+
clients := client.GetClients()
51+
52+
ownerClient := clients[0]
53+
54+
router, handlers := setupObjectTreeHandlers()
55+
56+
sch := zcncrypto.NewSignatureScheme("bls0chain")
57+
//sch.Mnemonic = "expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe"
58+
_, err := sch.RecoverKeys("expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe")
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
63+
ts := time.Now().Add(time.Hour)
64+
alloc := makeTestAllocation(common.Timestamp(ts.Unix()))
65+
alloc.OwnerPublicKey = ownerClient.Keys[0].PublicKey
66+
alloc.OwnerID = ownerClient.ClientID
67+
68+
const (
69+
path = "/path"
70+
newName = "new name"
71+
connectionID = "connection id"
72+
)
73+
74+
type (
75+
args struct {
76+
w *httptest.ResponseRecorder
77+
r *http.Request
78+
}
79+
test struct {
80+
name string
81+
args args
82+
alloc *allocation.Allocation
83+
setupDbMock func(mock sqlmock.Sqlmock)
84+
begin func()
85+
end func()
86+
wantCode int
87+
wantBody string
88+
}
89+
)
90+
negativeTests := make([]test, 0)
91+
for _, name := range handlers {
92+
if !isEndpointRequireSignature(name) {
93+
continue
94+
}
95+
96+
baseSetupDbMock := func(mock sqlmock.Sqlmock) {
97+
mock.ExpectBegin()
98+
99+
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)).
100+
WithArgs(alloc.Tx).
101+
WillReturnRows(
102+
sqlmock.NewRows([]string{"id", "tx", "expiration_date", "owner_public_key", "owner_id"}).
103+
AddRow(alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID),
104+
)
105+
106+
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)).
107+
WithArgs(alloc.ID).
108+
WillReturnRows(
109+
sqlmock.NewRows([]string{"id", "allocation_id"}).
110+
AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID),
111+
)
112+
113+
mock.ExpectCommit()
114+
}
115+
116+
emptySignature := test{
117+
name: name + "_Empty_Signature",
118+
args: args{
119+
w: httptest.NewRecorder(),
120+
r: func() *http.Request {
121+
url, err := router.Get(name).URL("allocation", alloc.Tx)
122+
if err != nil {
123+
t.Fatal()
124+
}
125+
126+
method := http.MethodGet
127+
if !isEndpointAllowGetReq(name) {
128+
method = http.MethodPost
129+
}
130+
r, err := http.NewRequest(method, url.String(), nil)
131+
if err != nil {
132+
t.Fatal(err)
133+
}
134+
135+
return r
136+
}(),
137+
},
138+
alloc: alloc,
139+
setupDbMock: baseSetupDbMock,
140+
wantCode: http.StatusBadRequest,
141+
wantBody: "{\"error\":\"invalid_signature: Invalid signature\"}\n",
142+
}
143+
negativeTests = append(negativeTests, emptySignature)
144+
145+
wrongSignature := test{
146+
name: name + "_Wrong_Signature",
147+
args: args{
148+
w: httptest.NewRecorder(),
149+
r: func() *http.Request {
150+
url, err := router.Get(name).URL("allocation", alloc.Tx)
151+
if err != nil {
152+
t.Fatal()
153+
}
154+
155+
method := http.MethodGet
156+
if !isEndpointAllowGetReq(name) {
157+
method = http.MethodPost
158+
}
159+
r, err := http.NewRequest(method, url.String(), nil)
160+
if err != nil {
161+
t.Fatal(err)
162+
}
163+
164+
hash := encryption.Hash("another data")
165+
sign, err := sch.Sign(hash)
166+
if err != nil {
167+
t.Fatal(err)
168+
}
169+
170+
r.Header.Set(common.ClientSignatureHeader, sign)
171+
r.Header.Set(common.ClientHeader, alloc.OwnerID)
172+
173+
return r
174+
}(),
175+
},
176+
alloc: alloc,
177+
setupDbMock: baseSetupDbMock,
178+
wantCode: http.StatusBadRequest,
179+
wantBody: "{\"error\":\"invalid_signature: Invalid signature\"}\n",
180+
}
181+
negativeTests = append(negativeTests, wrongSignature)
182+
}
183+
184+
positiveTests := []test{
185+
186+
{
187+
name: "Object_Tree_OK",
188+
args: args{
189+
w: httptest.NewRecorder(),
190+
r: func() *http.Request {
191+
handlerName := handlers["/v1/file/objecttree/{allocation}"]
192+
url, err := router.Get(handlerName).URL("allocation", alloc.Tx)
193+
if err != nil {
194+
t.Fatal()
195+
}
196+
q := url.Query()
197+
q.Set("path", path)
198+
url.RawQuery = q.Encode()
199+
200+
r, err := http.NewRequest(http.MethodGet, url.String(), nil)
201+
if err != nil {
202+
t.Fatal(err)
203+
}
204+
205+
hash := encryption.Hash(alloc.Tx)
206+
sign, err := sch.Sign(hash)
207+
if err != nil {
208+
t.Fatal(err)
209+
}
210+
211+
r.Header.Set(common.ClientSignatureHeader, sign)
212+
r.Header.Set(common.ClientHeader, alloc.OwnerID)
213+
214+
return r
215+
}(),
216+
},
217+
alloc: alloc,
218+
setupDbMock: func(mock sqlmock.Sqlmock) {
219+
mock.ExpectBegin()
220+
221+
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)).
222+
WithArgs(alloc.Tx).
223+
WillReturnRows(
224+
sqlmock.NewRows([]string{"id", "tx", "expiration_date", "owner_public_key", "owner_id"}).
225+
AddRow(alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID),
226+
)
227+
228+
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)).
229+
WithArgs(alloc.ID).
230+
WillReturnRows(
231+
sqlmock.NewRows([]string{"id", "allocation_id"}).
232+
AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID),
233+
)
234+
235+
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)).
236+
WithArgs(alloc.ID, path, path+"/%", alloc.ID).
237+
WillReturnRows(
238+
sqlmock.NewRows([]string{"path"}).
239+
AddRow("/"),
240+
)
241+
242+
mock.ExpectCommit()
243+
},
244+
wantCode: http.StatusOK,
245+
},
246+
}
247+
248+
tests := append(positiveTests, negativeTests...)
249+
250+
for _, test := range tests {
251+
t.Run(test.name, func(t *testing.T) {
252+
mock := datastore.MockTheStore(t)
253+
test.setupDbMock(mock)
254+
255+
if test.begin != nil {
256+
test.begin()
257+
}
258+
router.ServeHTTP(test.args.w, test.args.r)
259+
if test.end != nil {
260+
test.end()
261+
}
262+
263+
fmt.Printf("\nResponse body: %v", test.args.w.Body.String())
264+
assert.Equal(t, test.wantCode, test.args.w.Result().StatusCode)
265+
if test.wantCode != http.StatusOK || test.wantBody != "" {
266+
assert.Equal(t, test.wantBody, test.args.w.Body.String())
267+
}
268+
})
269+
}
270+
271+
curDir, err := os.Getwd()
272+
if err != nil {
273+
t.Fatal(err)
274+
}
275+
if err := os.RemoveAll(curDir + "/tmp"); err != nil {
276+
t.Fatal(err)
277+
}
278+
}

0 commit comments

Comments
 (0)