master
go 268 lines 8.71 KB
Raw
1 package tests
2
3 import (
4 "context"
5 "testing"
6
7 dag "github.com/ipfs/boxo/ipld/merkledag"
8 ft "github.com/ipfs/boxo/ipld/unixfs"
9 "github.com/ipfs/boxo/path"
10 ipld "github.com/ipfs/go-ipld-format"
11 iface "github.com/ipfs/kubo/core/coreiface"
12 opt "github.com/ipfs/kubo/core/coreiface/options"
13 "github.com/stretchr/testify/require"
14 )
15
16 func (tp *TestSuite) TestObject(t *testing.T) {
17 tp.hasApi(t, func(api iface.CoreAPI) error {
18 if api.Object() == nil {
19 return errAPINotImplemented
20 }
21 return nil
22 })
23
24 t.Run("TestObjectAddLink", tp.TestObjectAddLink)
25 t.Run("TestObjectAddLinkCreate", tp.TestObjectAddLinkCreate)
26 t.Run("TestObjectAddLinkValidation", tp.TestObjectAddLinkValidation)
27 t.Run("TestObjectRmLink", tp.TestObjectRmLink)
28 t.Run("TestObjectRmLinkValidation", tp.TestObjectRmLinkValidation)
29 t.Run("TestDiffTest", tp.TestDiffTest)
30 }
31
32 func putDagPbNode(t *testing.T, ctx context.Context, api iface.CoreAPI, data string, links []*ipld.Link) path.ImmutablePath {
33 dagnode := new(dag.ProtoNode)
34
35 if data != "" {
36 dagnode.SetData([]byte(data))
37 }
38
39 if links != nil {
40 err := dagnode.SetLinks(links)
41 require.NoError(t, err)
42 }
43
44 err := api.Dag().Add(ctx, dagnode)
45 require.NoError(t, err)
46
47 return path.FromCid(dagnode.Cid())
48 }
49
50 func (tp *TestSuite) TestObjectAddLink(t *testing.T) {
51 ctx := t.Context()
52 api, err := tp.makeAPI(t, ctx)
53 require.NoError(t, err)
54
55 p1 := putDagPbNode(t, ctx, api, "foo", nil)
56 p2 := putDagPbNode(t, ctx, api, "bazz", []*ipld.Link{
57 {
58 Name: "bar",
59 Cid: p1.RootCid(),
60 Size: 3,
61 },
62 })
63
64 // Raw dag-pb nodes require SkipUnixFSValidation since they have no UnixFS metadata
65 p3, err := api.Object().AddLink(ctx, p2, "abc", p2, opt.Object.SkipUnixFSValidation(true))
66 require.NoError(t, err)
67
68 nd, err := api.Dag().Get(ctx, p3.RootCid())
69 require.NoError(t, err)
70
71 links := nd.Links()
72 require.Len(t, links, 2)
73 require.Equal(t, "abc", links[0].Name)
74 require.Equal(t, "bar", links[1].Name)
75 }
76
77 func (tp *TestSuite) TestObjectAddLinkCreate(t *testing.T) {
78 ctx := t.Context()
79 api, err := tp.makeAPI(t, ctx)
80 require.NoError(t, err)
81
82 p1 := putDagPbNode(t, ctx, api, "foo", nil)
83 p2 := putDagPbNode(t, ctx, api, "bazz", []*ipld.Link{
84 {
85 Name: "bar",
86 Cid: p1.RootCid(),
87 Size: 3,
88 },
89 })
90
91 // Raw dag-pb nodes require SkipUnixFSValidation since they have no UnixFS metadata
92 _, err = api.Object().AddLink(ctx, p2, "abc/d", p2, opt.Object.SkipUnixFSValidation(true))
93 require.ErrorContains(t, err, "no link by that name")
94
95 p3, err := api.Object().AddLink(ctx, p2, "abc/d", p2, opt.Object.Create(true), opt.Object.SkipUnixFSValidation(true))
96 require.NoError(t, err)
97
98 nd, err := api.Dag().Get(ctx, p3.RootCid())
99 require.NoError(t, err)
100
101 links := nd.Links()
102 require.Len(t, links, 2)
103 require.Equal(t, "abc", links[0].Name)
104 require.Equal(t, "bar", links[1].Name)
105 }
106
107 // TestObjectAddLinkValidation verifies that AddLink rejects non-directory
108 // nodes by default, preventing the data-loss bug in
109 // https://github.com/ipfs/kubo/issues/7190
110 func (tp *TestSuite) TestObjectAddLinkValidation(t *testing.T) {
111 ctx := t.Context()
112 api, err := tp.makeAPI(t, ctx)
113 require.NoError(t, err)
114
115 child := putDagPbNode(t, ctx, api, "child", nil)
116
117 // UnixFS Directory: allowed
118 dirNode := ft.EmptyDirNode()
119 err = api.Dag().Add(ctx, dirNode)
120 require.NoError(t, err)
121 dirPath := path.FromCid(dirNode.Cid())
122
123 _, err = api.Object().AddLink(ctx, dirPath, "foo", child)
124 require.NoError(t, err)
125
126 // UnixFS File: rejected (would cause data loss on read-back)
127 fileNode := ft.EmptyFileNode()
128 err = api.Dag().Add(ctx, fileNode)
129 require.NoError(t, err)
130 filePath := path.FromCid(fileNode.Cid())
131
132 _, err = api.Object().AddLink(ctx, filePath, "foo", child)
133 require.ErrorContains(t, err, "cannot add named links to a UnixFS File node, only Directory nodes support link addition at the dag-pb level")
134
135 // UnixFS File with SkipUnixFSValidation: allowed (user takes responsibility)
136 _, err = api.Object().AddLink(ctx, filePath, "foo", child, opt.Object.SkipUnixFSValidation(true))
137 require.NoError(t, err)
138
139 // HAMTShard: rejected (dag-pb level mutation corrupts HAMT bitfield)
140 hamtData, err := ft.HAMTShardData(nil, 256, 0x22)
141 require.NoError(t, err)
142 hamtNode := new(dag.ProtoNode)
143 hamtNode.SetData(hamtData)
144 err = api.Dag().Add(ctx, hamtNode)
145 require.NoError(t, err)
146 hamtPath := path.FromCid(hamtNode.Cid())
147
148 _, err = api.Object().AddLink(ctx, hamtPath, "foo", child)
149 require.ErrorContains(t, err, "cannot add links to a HAMTShard at the dag-pb level (would corrupt the HAMT bitfield); use 'ipfs files' commands instead, or pass --allow-non-unixfs to override")
150
151 // HAMTShard with SkipUnixFSValidation: allowed
152 _, err = api.Object().AddLink(ctx, hamtPath, "foo", child, opt.Object.SkipUnixFSValidation(true))
153 require.NoError(t, err)
154
155 // Raw dag-pb (no UnixFS data): rejected
156 rawPb := putDagPbNode(t, ctx, api, "", nil)
157
158 _, err = api.Object().AddLink(ctx, rawPb, "foo", child)
159 require.ErrorContains(t, err, "cannot add named links to a non-UnixFS dag-pb node; pass --allow-non-unixfs to skip validation")
160
161 // Raw dag-pb with SkipUnixFSValidation: allowed
162 _, err = api.Object().AddLink(ctx, rawPb, "foo", child, opt.Object.SkipUnixFSValidation(true))
163 require.NoError(t, err)
164 }
165
166 func (tp *TestSuite) TestObjectRmLink(t *testing.T) {
167 ctx := t.Context()
168 api, err := tp.makeAPI(t, ctx)
169 require.NoError(t, err)
170
171 p1 := putDagPbNode(t, ctx, api, "foo", nil)
172 p2 := putDagPbNode(t, ctx, api, "bazz", []*ipld.Link{
173 {
174 Name: "bar",
175 Cid: p1.RootCid(),
176 Size: 3,
177 },
178 })
179
180 // Raw dag-pb nodes require SkipUnixFSValidation since they have no UnixFS metadata
181 p3, err := api.Object().RmLink(ctx, p2, "bar", opt.Object.RmLinkSkipUnixFSValidation(true))
182 require.NoError(t, err)
183
184 nd, err := api.Dag().Get(ctx, p3.RootCid())
185 require.NoError(t, err)
186
187 links := nd.Links()
188 require.Len(t, links, 0)
189 }
190
191 // TestObjectRmLinkValidation verifies that RmLink rejects non-directory
192 // nodes by default, preventing silent DAG corruption.
193 func (tp *TestSuite) TestObjectRmLinkValidation(t *testing.T) {
194 ctx := t.Context()
195 api, err := tp.makeAPI(t, ctx)
196 require.NoError(t, err)
197
198 child := putDagPbNode(t, ctx, api, "child", nil)
199
200 // UnixFS Directory with a link: rm-link allowed
201 dirNode := ft.EmptyDirNode()
202 childNd, err := api.Dag().Get(ctx, child.RootCid())
203 require.NoError(t, err)
204 err = dirNode.AddNodeLink("foo", childNd)
205 require.NoError(t, err)
206 err = api.Dag().Add(ctx, dirNode)
207 require.NoError(t, err)
208 dirPath := path.FromCid(dirNode.Cid())
209
210 _, err = api.Object().RmLink(ctx, dirPath, "foo")
211 require.NoError(t, err)
212
213 // UnixFS File: rejected
214 fileNode := ft.EmptyFileNode()
215 err = api.Dag().Add(ctx, fileNode)
216 require.NoError(t, err)
217 filePath := path.FromCid(fileNode.Cid())
218
219 _, err = api.Object().RmLink(ctx, filePath, "foo")
220 require.ErrorContains(t, err, "cannot remove links from a UnixFS File node, only Directory nodes support link removal at the dag-pb level")
221
222 // UnixFS File with SkipUnixFSValidation: allowed
223 _, err = api.Object().RmLink(ctx, filePath, "foo", opt.Object.RmLinkSkipUnixFSValidation(true))
224 // ErrLinkNotFound is expected since the file has no links, but validation passed
225 require.ErrorContains(t, err, "no link by that name")
226
227 // HAMTShard: rejected
228 hamtData, err := ft.HAMTShardData(nil, 256, 0x22)
229 require.NoError(t, err)
230 hamtNode := new(dag.ProtoNode)
231 hamtNode.SetData(hamtData)
232 err = api.Dag().Add(ctx, hamtNode)
233 require.NoError(t, err)
234 hamtPath := path.FromCid(hamtNode.Cid())
235
236 _, err = api.Object().RmLink(ctx, hamtPath, "foo")
237 require.ErrorContains(t, err, "cannot remove links from a HAMTShard at the dag-pb level (would corrupt the HAMT bitfield); use 'ipfs files rm' instead, or pass --allow-non-unixfs to override")
238
239 // HAMTShard with SkipUnixFSValidation: allowed (validation bypassed)
240 _, err = api.Object().RmLink(ctx, hamtPath, "foo", opt.Object.RmLinkSkipUnixFSValidation(true))
241 require.ErrorContains(t, err, "no link by that name")
242
243 // Raw dag-pb (no UnixFS data): rejected
244 rawPb := putDagPbNode(t, ctx, api, "", nil)
245
246 _, err = api.Object().RmLink(ctx, rawPb, "foo")
247 require.ErrorContains(t, err, "cannot remove links from a non-UnixFS dag-pb node; pass --allow-non-unixfs to skip validation")
248
249 // Raw dag-pb with SkipUnixFSValidation: allowed
250 _, err = api.Object().RmLink(ctx, rawPb, "foo", opt.Object.RmLinkSkipUnixFSValidation(true))
251 require.ErrorContains(t, err, "no link by that name")
252 }
253
254 func (tp *TestSuite) TestDiffTest(t *testing.T) {
255 ctx := t.Context()
256 api, err := tp.makeAPI(t, ctx)
257 require.NoError(t, err)
258
259 p1 := putDagPbNode(t, ctx, api, "foo", nil)
260 p2 := putDagPbNode(t, ctx, api, "bar", nil)
261
262 changes, err := api.Object().Diff(ctx, p1, p2)
263 require.NoError(t, err)
264 require.Len(t, changes, 1)
265 require.Equal(t, iface.DiffMod, changes[0].Type)
266 require.Equal(t, p1.String(), changes[0].Before.String())
267 require.Equal(t, p2.String(), changes[0].After.String())
268 }