@cryptotaxi247 / kubo / commits / 0377e4959

coreapi: object API tests

License: MIT Signed-off-by: Łukasz Magiera <magik6k@gmail.com>

Łukasz Magiera committed Jan 5, 2018 at 01:13 UTC 0377e49590108c150114d53fe9df9de26a87912d
5 files changed +525 -15
core/coreapi/coreapi.go
+1
@@ -41,6 +41,7 @@ func (api *CoreAPI) Key() coreiface.KeyAPI {
41 return &KeyAPI{api, nil}
42 }
43
44 +//Object returns the ObjectAPI interface backed by the go-ipfs node
45 func (api *CoreAPI) Object() coreiface.ObjectAPI {
46 return &ObjectAPI{api, nil}
47 }
core/coreapi/interface/interface.go
+13 -5
@@ -219,6 +219,14 @@ type ObjectAPI interface {
219 // * "json"
220 WithInputEnc(e string) options.ObjectPutOption
221
222 + // WithDataType specifies the encoding of data field when using Josn or XML
223 + // input encoding.
224 + //
225 + // Supported types:
226 + // * "text" (default)
227 + // * "base64"
228 + WithDataType(t string) options.ObjectPutOption
229 +
230 // Get returns the node for the path
231 Get(context.Context, Path) (Node, error)
232
@@ -234,20 +242,20 @@ type ObjectAPI interface {
242 // AddLink adds a link under the specified path. child path can point to a
243 // subdirectory within the patent which must be present (can be overridden
244 // with WithCreate option).
237 - AddLink(ctx context.Context, base Path, name string, child Path, opts ...options.ObjectAddLinkOption) (Node, error)
245 + AddLink(ctx context.Context, base Path, name string, child Path, opts ...options.ObjectAddLinkOption) (Path, error)
246
247 // WithCreate is an option for AddLink which specifies whether create required
248 // directories for the child
249 WithCreate(create bool) options.ObjectAddLinkOption
250
251 // RmLink removes a link from the node
244 - RmLink(ctx context.Context, base Path, link string) (Node, error)
252 + RmLink(ctx context.Context, base Path, link string) (Path, error)
253
254 // AppendData appends data to the node
247 - AppendData(context.Context, Path, io.Reader) (Node, error)
255 + AppendData(context.Context, Path, io.Reader) (Path, error)
256
257 // SetData sets the data contained in the node
250 - SetData(context.Context, Path, io.Reader) (Node, error)
258 + SetData(context.Context, Path, io.Reader) (Path, error)
259 }
260
261 // ObjectStat provides information about dag nodes
@@ -267,7 +275,7 @@ type ObjectStat struct {
275 // DataSize is the size of data block section
276 DataSize int
277
270 - // CumulativeSize is size of node
278 + // CumulativeSize is size of the tree (BlockSize + link sizes)
279 CumulativeSize int
280 }
281
core/coreapi/interface/options/object.go
+9
@@ -6,6 +6,7 @@ type ObjectNewSettings struct {
6
7 type ObjectPutSettings struct {
8 InputEnc string
9 + DataType string
10 }
11
12 type ObjectAddLinkSettings struct {
@@ -33,6 +34,7 @@ func ObjectNewOptions(opts ...ObjectNewOption) (*ObjectNewSettings, error) {
34 func ObjectPutOptions(opts ...ObjectPutOption) (*ObjectPutSettings, error) {
35 options := &ObjectPutSettings{
36 InputEnc: "json",
37 + DataType: "text",
38 }
39
40 for _, opt := range opts {
@@ -74,6 +76,13 @@ func (api *ObjectOptions) WithInputEnc(e string) ObjectPutOption {
76 }
77 }
78
79 +func (api *ObjectOptions) WithDataType(t string) ObjectPutOption {
80 + return func(settings *ObjectPutSettings) error {
81 + settings.DataType = t
82 + return nil
83 + }
84 +}
85 +
86 func (api *ObjectOptions) WithCreate(create bool) ObjectAddLinkOption {
87 return func(settings *ObjectAddLinkSettings) error {
88 settings.Create = create
core/coreapi/object.go
+117 -10
@@ -13,15 +13,32 @@ import (
13 dag "github.com/ipfs/go-ipfs/merkledag"
14 ft "github.com/ipfs/go-ipfs/unixfs"
15
16 + "encoding/base64"
17 + "encoding/json"
18 + "encoding/xml"
19 + "errors"
20 + "fmt"
21 node "gx/ipfs/QmNwUEK7QbwSqyKBu3mMtToo8SUc6wQJ7gdZq4gGGJqfnf/go-ipld-format"
22 cid "gx/ipfs/QmeSrf6pzut73u6zLQkRFQ3ygt3k6XFT2kjdYP8Tnkwwyg/go-cid"
23 )
24
25 +const inputLimit = 2 << 20
26 +
27 type ObjectAPI struct {
28 *CoreAPI
29 *caopts.ObjectOptions
30 }
31
32 +type Link struct {
33 + Name, Hash string
34 + Size uint64
35 +}
36 +
37 +type Node struct {
38 + Links []Link
39 + Data string
40 +}
41 +
42 func (api *ObjectAPI) New(ctx context.Context, opts ...caopts.ObjectNewOption) (coreiface.Node, error) {
43 options, err := caopts.ObjectNewOptions(opts...)
44 if err != nil {
@@ -49,8 +66,66 @@ func (api *ObjectAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.Obj
66 return nil, err
67 }
68
52 - dagApi := api.Dag()
53 - return dagApi.Put(ctx, src, dagApi.WithInputEnc(options.InputEnc), dagApi.WithCodec(cid.DagProtobuf))
69 + data, err := ioutil.ReadAll(io.LimitReader(src, inputLimit+10))
70 + if err != nil {
71 + return nil, err
72 + }
73 +
74 + var dagnode *dag.ProtoNode
75 + switch options.InputEnc {
76 + case "json":
77 + node := new(Node)
78 + err = json.Unmarshal(data, node)
79 + if err != nil {
80 + return nil, err
81 + }
82 +
83 + // check that we have data in the Node to add
84 + // otherwise we will add the empty object without raising an error
85 + if nodeEmpty(node) {
86 + return nil, errors.New("no data or links in this node")
87 + }
88 +
89 + dagnode, err = deserializeNode(node, options.DataType)
90 + if err != nil {
91 + return nil, err
92 + }
93 +
94 + case "protobuf":
95 + dagnode, err = dag.DecodeProtobuf(data)
96 +
97 + case "xml":
98 + node := new(Node)
99 + err = xml.Unmarshal(data, node)
100 + if err != nil {
101 + return nil, err
102 + }
103 +
104 + // check that we have data in the Node to add
105 + // otherwise we will add the empty object without raising an error
106 + if nodeEmpty(node) {
107 + return nil, errors.New("no data or links in this node")
108 + }
109 +
110 + dagnode, err = deserializeNode(node, options.DataType)
111 + if err != nil {
112 + return nil, err
113 + }
114 +
115 + default:
116 + return nil, errors.New("unknown object encoding")
117 + }
118 +
119 + if err != nil {
120 + return nil, err
121 + }
122 +
123 + _, err = api.node.DAG.Add(dagnode)
124 + if err != nil {
125 + return nil, err
126 + }
127 +
128 + return ParseCid(dagnode.Cid()), nil
129 }
130
131 func (api *ObjectAPI) Get(ctx context.Context, path coreiface.Path) (coreiface.Node, error) {
@@ -109,7 +184,7 @@ func (api *ObjectAPI) Stat(ctx context.Context, path coreiface.Path) (*coreiface
184 return out, nil
185 }
186
112 -func (api *ObjectAPI) AddLink(ctx context.Context, base coreiface.Path, name string, child coreiface.Path, opts ...caopts.ObjectAddLinkOption) (coreiface.Node, error) {
187 +func (api *ObjectAPI) AddLink(ctx context.Context, base coreiface.Path, name string, child coreiface.Path, opts ...caopts.ObjectAddLinkOption) (coreiface.Path, error) {
188 options, err := caopts.ObjectAddLinkOptions(opts...)
189 if err != nil {
190 return nil, err
@@ -147,10 +222,10 @@ func (api *ObjectAPI) AddLink(ctx context.Context, base coreiface.Path, name str
222 return nil, err
223 }
224
150 - return nnode, nil
225 + return ParseCid(nnode.Cid()), nil
226 }
227
153 -func (api *ObjectAPI) RmLink(ctx context.Context, base coreiface.Path, link string) (coreiface.Node, error) {
228 +func (api *ObjectAPI) RmLink(ctx context.Context, base coreiface.Path, link string) (coreiface.Path, error) {
229 baseNd, err := api.core().ResolveNode(ctx, base)
230 if err != nil {
231 return nil, err
@@ -173,18 +248,18 @@ func (api *ObjectAPI) RmLink(ctx context.Context, base coreiface.Path, link stri
248 return nil, err
249 }
250
176 - return nnode, nil
251 + return ParseCid(nnode.Cid()), nil
252 }
253
179 -func (api *ObjectAPI) AppendData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.Node, error) {
254 +func (api *ObjectAPI) AppendData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.Path, error) {
255 return api.patchData(ctx, path, r, true)
256 }
257
183 -func (api *ObjectAPI) SetData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.Node, error) {
258 +func (api *ObjectAPI) SetData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.Path, error) {
259 return api.patchData(ctx, path, r, false)
260 }
261
187 -func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.Reader, appendData bool) (coreiface.Node, error) {
262 +func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.Reader, appendData bool) (coreiface.Path, error) {
263 nd, err := api.core().ResolveNode(ctx, path)
264 if err != nil {
265 return nil, err
@@ -210,9 +285,41 @@ func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.R
285 return nil, err
286 }
287
213 - return pbnd, nil
288 + return ParseCid(pbnd.Cid()), nil
289 }
290
291 func (api *ObjectAPI) core() coreiface.CoreAPI {
292 return api.CoreAPI
293 }
294 +
295 +func deserializeNode(nd *Node, dataFieldEncoding string) (*dag.ProtoNode, error) {
296 + dagnode := new(dag.ProtoNode)
297 + switch dataFieldEncoding {
298 + case "text":
299 + dagnode.SetData([]byte(nd.Data))
300 + case "base64":
301 + data, _ := base64.StdEncoding.DecodeString(nd.Data)
302 + dagnode.SetData(data)
303 + default:
304 + return nil, fmt.Errorf("Unkown data field encoding")
305 + }
306 +
307 + dagnode.SetLinks(make([]*node.Link, len(nd.Links)))
308 + for i, link := range nd.Links {
309 + c, err := cid.Decode(link.Hash)
310 + if err != nil {
311 + return nil, err
312 + }
313 + dagnode.Links()[i] = &node.Link{
314 + Name: link.Name,
315 + Size: link.Size,
316 + Cid: c,
317 + }
318 + }
319 +
320 + return dagnode, nil
321 +}
322 +
323 +func nodeEmpty(node *Node) bool {
324 + return node.Data == "" && len(node.Links) == 0
325 +}
core/coreapi/object_test.go new
+385
@@ -0,0 +1,385 @@
1 +package coreapi_test
2 +
3 +import (
4 + "bytes"
5 + "context"
6 + "encoding/hex"
7 + "io/ioutil"
8 + "strings"
9 + "testing"
10 +)
11 +
12 +func TestNew(t *testing.T) {
13 + ctx := context.Background()
14 + _, api, err := makeAPI(ctx)
15 + if err != nil {
16 + t.Fatal(err)
17 + }
18 +
19 + emptyNode, err := api.Object().New(ctx)
20 + if err != nil {
21 + t.Fatal(err)
22 + }
23 +
24 + dirNode, err := api.Object().New(ctx, api.Object().WithType("unixfs-dir"))
25 + if err != nil {
26 + t.Fatal(err)
27 + }
28 +
29 + if emptyNode.String() != "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n" {
30 + t.Errorf("Unexpected emptyNode path: %s", emptyNode.String())
31 + }
32 +
33 + if dirNode.String() != "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" {
34 + t.Errorf("Unexpected dirNode path: %s", dirNode.String())
35 + }
36 +}
37 +
38 +func TestObjectPut(t *testing.T) {
39 + ctx := context.Background()
40 + _, api, err := makeAPI(ctx)
41 + if err != nil {
42 + t.Fatal(err)
43 + }
44 +
45 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
46 + if err != nil {
47 + t.Fatal(err)
48 + }
49 +
50 + p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"YmFy"}`), api.Object().WithDataType("base64")) //bar
51 + if err != nil {
52 + t.Fatal(err)
53 + }
54 +
55 + pbBytes, err := hex.DecodeString("0a0362617a")
56 + if err != nil {
57 + t.Fatal(err)
58 + }
59 +
60 + p3, err := api.Object().Put(ctx, bytes.NewReader(pbBytes), api.Object().WithInputEnc("protobuf"))
61 + if err != nil {
62 + t.Fatal(err)
63 + }
64 +
65 + if p1.String() != "/ipfs/QmQeGyS87nyijii7kFt1zbe4n2PsXTFimzsdxyE9qh9TST" {
66 + t.Errorf("unexpected path: %s", p1.String())
67 + }
68 +
69 + if p2.String() != "/ipfs/QmNeYRbCibmaMMK6Du6ChfServcLqFvLJF76PzzF76SPrZ" {
70 + t.Errorf("unexpected path: %s", p2.String())
71 + }
72 +
73 + if p3.String() != "/ipfs/QmZreR7M2t7bFXAdb1V5FtQhjk4t36GnrvueLJowJbQM9m" {
74 + t.Errorf("unexpected path: %s", p3.String())
75 + }
76 +}
77 +
78 +func TestObjectGet(t *testing.T) {
79 + ctx := context.Background()
80 + _, api, err := makeAPI(ctx)
81 + if err != nil {
82 + t.Fatal(err)
83 + }
84 +
85 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
86 + if err != nil {
87 + t.Fatal(err)
88 + }
89 +
90 + nd, err := api.Object().Get(ctx, p1)
91 + if err != nil {
92 + t.Fatal(err)
93 + }
94 +
95 + if string(nd.RawData()[len(nd.RawData())-3:]) != "foo" {
96 + t.Fatal("got non-matching data")
97 + }
98 +}
99 +
100 +func TestObjectData(t *testing.T) {
101 + ctx := context.Background()
102 + _, api, err := makeAPI(ctx)
103 + if err != nil {
104 + t.Fatal(err)
105 + }
106 +
107 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
108 + if err != nil {
109 + t.Fatal(err)
110 + }
111 +
112 + r, err := api.Object().Data(ctx, p1)
113 + if err != nil {
114 + t.Fatal(err)
115 + }
116 +
117 + data, err := ioutil.ReadAll(r)
118 + if err != nil {
119 + t.Fatal(err)
120 + }
121 +
122 + if string(data) != "foo" {
123 + t.Fatal("got non-matching data")
124 + }
125 +}
126 +
127 +func TestObjectLinks(t *testing.T) {
128 + ctx := context.Background()
129 + _, api, err := makeAPI(ctx)
130 + if err != nil {
131 + t.Fatal(err)
132 + }
133 +
134 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
135 + if err != nil {
136 + t.Fatal(err)
137 + }
138 +
139 + p2, err := api.Object().Put(ctx, strings.NewReader(`{"Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`"}]}`))
140 + if err != nil {
141 + t.Fatal(err)
142 + }
143 +
144 + links, err := api.Object().Links(ctx, p2)
145 + if err != nil {
146 + t.Fatal(err)
147 + }
148 +
149 + if len(links) != 1 {
150 + t.Errorf("unexpected number of links: %d", len(links))
151 + }
152 +
153 + if links[0].Cid.String() != p1.Cid().String() {
154 + t.Fatal("cids didn't batch")
155 + }
156 +
157 + if links[0].Name != "bar" {
158 + t.Fatal("unexpected link name")
159 + }
160 +}
161 +
162 +func TestObjectStat(t *testing.T) {
163 + ctx := context.Background()
164 + _, api, err := makeAPI(ctx)
165 + if err != nil {
166 + t.Fatal(err)
167 + }
168 +
169 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
170 + if err != nil {
171 + t.Fatal(err)
172 + }
173 +
174 + p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bazz", "Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`", "Size":3}]}`))
175 + if err != nil {
176 + t.Fatal(err)
177 + }
178 +
179 + stat, err := api.Object().Stat(ctx, p2)
180 + if err != nil {
181 + t.Fatal(err)
182 + }
183 +
184 + if stat.Cid.String() != p2.Cid().String() {
185 + t.Error("unexpected stat.Cid")
186 + }
187 +
188 + if stat.NumLinks != 1 {
189 + t.Errorf("unexpected stat.NumLinks")
190 + }
191 +
192 + if stat.BlockSize != 51 {
193 + t.Error("unexpected stat.BlockSize")
194 + }
195 +
196 + if stat.LinksSize != 47 {
197 + t.Errorf("unexpected stat.LinksSize: %d", stat.LinksSize)
198 + }
199 +
200 + if stat.DataSize != 4 {
201 + t.Error("unexpected stat.DataSize")
202 + }
203 +
204 + if stat.CumulativeSize != 54 {
205 + t.Error("unexpected stat.DataSize")
206 + }
207 +}
208 +
209 +func TestObjectAddLink(t *testing.T) {
210 + ctx := context.Background()
211 + _, api, err := makeAPI(ctx)
212 + if err != nil {
213 + t.Fatal(err)
214 + }
215 +
216 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
217 + if err != nil {
218 + t.Fatal(err)
219 + }
220 +
221 + p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bazz", "Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`", "Size":3}]}`))
222 + if err != nil {
223 + t.Fatal(err)
224 + }
225 +
226 + p3, err := api.Object().AddLink(ctx, p2, "abc", p2)
227 + if err != nil {
228 + t.Fatal(err)
229 + }
230 +
231 + links, err := api.Object().Links(ctx, p3)
232 + if err != nil {
233 + t.Fatal(err)
234 + }
235 +
236 + if len(links) != 2 {
237 + t.Errorf("unexpected number of links: %d", len(links))
238 + }
239 +
240 + if links[0].Name != "abc" {
241 + t.Errorf("unexpected link 0 name: %s", links[0].Name)
242 + }
243 +
244 + if links[1].Name != "bar" {
245 + t.Errorf("unexpected link 1 name: %s", links[1].Name)
246 + }
247 +}
248 +
249 +func TestObjectAddLinkCreate(t *testing.T) {
250 + ctx := context.Background()
251 + _, api, err := makeAPI(ctx)
252 + if err != nil {
253 + t.Fatal(err)
254 + }
255 +
256 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
257 + if err != nil {
258 + t.Fatal(err)
259 + }
260 +
261 + p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bazz", "Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`", "Size":3}]}`))
262 + if err != nil {
263 + t.Fatal(err)
264 + }
265 +
266 + p3, err := api.Object().AddLink(ctx, p2, "abc/d", p2)
267 + if err == nil {
268 + t.Fatal("expected an error")
269 + }
270 + if err.Error() != "no link by that name" {
271 + t.Fatal("unexpected error: %s", err.Error())
272 + }
273 +
274 + p3, err = api.Object().AddLink(ctx, p2, "abc/d", p2, api.Object().WithCreate(true))
275 + if err != nil {
276 + t.Fatal(err)
277 + }
278 +
279 + links, err := api.Object().Links(ctx, p3)
280 + if err != nil {
281 + t.Fatal(err)
282 + }
283 +
284 + if len(links) != 2 {
285 + t.Errorf("unexpected number of links: %d", len(links))
286 + }
287 +
288 + if links[0].Name != "abc" {
289 + t.Errorf("unexpected link 0 name: %s", links[0].Name)
290 + }
291 +
292 + if links[1].Name != "bar" {
293 + t.Errorf("unexpected link 1 name: %s", links[1].Name)
294 + }
295 +}
296 +
297 +func TestObjectRmLink(t *testing.T) {
298 + ctx := context.Background()
299 + _, api, err := makeAPI(ctx)
300 + if err != nil {
301 + t.Fatal(err)
302 + }
303 +
304 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
305 + if err != nil {
306 + t.Fatal(err)
307 + }
308 +
309 + p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bazz", "Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`", "Size":3}]}`))
310 + if err != nil {
311 + t.Fatal(err)
312 + }
313 +
314 + p3, err := api.Object().RmLink(ctx, p2, "bar")
315 + if err != nil {
316 + t.Fatal(err)
317 + }
318 +
319 + links, err := api.Object().Links(ctx, p3)
320 + if err != nil {
321 + t.Fatal(err)
322 + }
323 +
324 + if len(links) != 0 {
325 + t.Errorf("unexpected number of links: %d", len(links))
326 + }
327 +}
328 +
329 +func TestObjectAddData(t *testing.T) {
330 + ctx := context.Background()
331 + _, api, err := makeAPI(ctx)
332 + if err != nil {
333 + t.Fatal(err)
334 + }
335 +
336 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
337 + if err != nil {
338 + t.Fatal(err)
339 + }
340 +
341 + p2, err := api.Object().AppendData(ctx, p1, strings.NewReader("bar"))
342 + if err != nil {
343 + t.Fatal(err)
344 + }
345 +
346 + r, err := api.Object().Data(ctx, p2)
347 + if err != nil {
348 + t.Fatal(err)
349 + }
350 +
351 + data, err := ioutil.ReadAll(r)
352 +
353 + if string(data) != "foobar" {
354 + t.Error("unexpected data")
355 + }
356 +}
357 +
358 +func TestObjectSetData(t *testing.T) {
359 + ctx := context.Background()
360 + _, api, err := makeAPI(ctx)
361 + if err != nil {
362 + t.Fatal(err)
363 + }
364 +
365 + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
366 + if err != nil {
367 + t.Fatal(err)
368 + }
369 +
370 + p2, err := api.Object().SetData(ctx, p1, strings.NewReader("bar"))
371 + if err != nil {
372 + t.Fatal(err)
373 + }
374 +
375 + r, err := api.Object().Data(ctx, p2)
376 + if err != nil {
377 + t.Fatal(err)
378 + }
379 +
380 + data, err := ioutil.ReadAll(r)
381 +
382 + if string(data) != "bar" {
383 + t.Error("unexpected data")
384 + }
385 +}