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
+}