master
go 775 lines 26.1 KB
Raw
1 package cli
2
3 import (
4 "encoding/json"
5 "fmt"
6 "strings"
7 "testing"
8
9 cid "github.com/ipfs/go-cid"
10 "github.com/ipfs/kubo/test/cli/harness"
11 peer "github.com/libp2p/go-libp2p/core/peer"
12 mhash "github.com/multiformats/go-multihash"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15 )
16
17 func TestCidCommands(t *testing.T) {
18 t.Parallel()
19
20 t.Run("inspect", testCidInspect)
21 t.Run("base32", testCidBase32)
22 t.Run("format", testCidFormat)
23 t.Run("bases", testCidBases)
24 t.Run("codecs", testCidCodecs)
25 t.Run("hashes", testCidHashes)
26 }
27
28 // testCidInspect tests 'ipfs cid inspect' subcommand
29 func testCidInspect(t *testing.T) {
30 t.Parallel()
31 node := harness.NewT(t).NewNode()
32
33 t.Run("CIDv0", func(t *testing.T) {
34 res := node.RunIPFS("cid", "inspect", "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR")
35 assert.Equal(t, 0, res.ExitCode())
36 out := res.Stdout.String()
37 assert.Contains(t, out, "CID: QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR")
38 assert.Contains(t, out, "Version: 0")
39 assert.Contains(t, out, "Multibase: base58btc (implicit)")
40 assert.Contains(t, out, "Multicodec: dag-pb (0x70, implicit)")
41 assert.Contains(t, out, "Multihash: sha2-256 (0x12, implicit)")
42 assert.Contains(t, out, " Length: 32 bytes")
43 assert.Contains(t, out, " Digest: c3c4733ec8affd06cf9e9ff50ffc6bcd2ec85a6170004bb709669c31de94391a")
44 assert.Contains(t, out, "CIDv0: QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR")
45 assert.Contains(t, out, "CIDv1: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
46 })
47
48 t.Run("CIDv1 base32 dag-pb", func(t *testing.T) {
49 res := node.RunIPFS("cid", "inspect", "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
50 assert.Equal(t, 0, res.ExitCode())
51 out := res.Stdout.String()
52 assert.Contains(t, out, "CID: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
53 assert.Contains(t, out, "Version: 1")
54 assert.Contains(t, out, "Multibase: base32 (b)")
55 assert.Contains(t, out, "Multicodec: dag-pb (0x70)")
56 assert.Contains(t, out, "Multihash: sha2-256 (0x12)")
57 assert.Contains(t, out, " Length: 32 bytes")
58 assert.Contains(t, out, " Digest: c3c4733ec8affd06cf9e9ff50ffc6bcd2ec85a6170004bb709669c31de94391a")
59 assert.NotContains(t, out, "implicit")
60 assert.Contains(t, out, "CIDv0: QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR")
61 assert.Contains(t, out, "CIDv1: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
62 })
63
64 t.Run("CIDv1 raw codec", func(t *testing.T) {
65 res := node.RunIPFS("cid", "inspect", "bafkreigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
66 assert.Equal(t, 0, res.ExitCode())
67 out := res.Stdout.String()
68 assert.Contains(t, out, "CID: bafkreigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
69 assert.Contains(t, out, "Multibase: base32 (b)")
70 assert.Contains(t, out, "Multicodec: raw (0x55)")
71 assert.Contains(t, out, "Multihash: sha2-256 (0x12)")
72 assert.Contains(t, out, " Length: 32 bytes")
73 assert.Contains(t, out, " Digest: c3c4733ec8affd06cf9e9ff50ffc6bcd2ec85a6170004bb709669c31de94391a")
74 assert.Contains(t, out, "CIDv0: not possible, requires dag-pb (0x70), got raw (0x55)")
75 assert.Contains(t, out, "CIDv1: bafkreigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
76 })
77
78 t.Run("CIDv1 base36", func(t *testing.T) {
79 res := node.RunIPFS("cid", "inspect", "k2jmtxw8rjh1z69c6not3wtdxb0u3urbzhyll1t9jg6ox26dhi5sfi1m")
80 assert.Equal(t, 0, res.ExitCode())
81 out := res.Stdout.String()
82 assert.Contains(t, out, "CID: k2jmtxw8rjh1z69c6not3wtdxb0u3urbzhyll1t9jg6ox26dhi5sfi1m")
83 assert.Contains(t, out, "Multibase: base36 (k)")
84 assert.Contains(t, out, "Multicodec: dag-pb (0x70)")
85 assert.Contains(t, out, " Digest: c3c4733ec8affd06cf9e9ff50ffc6bcd2ec85a6170004bb709669c31de94391a")
86 assert.Contains(t, out, "CIDv0: QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR")
87 assert.Contains(t, out, "CIDv1: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
88 })
89
90 t.Run("invalid CID", func(t *testing.T) {
91 res := node.RunIPFS("cid", "inspect", "garbage")
92 assert.Equal(t, 1, res.ExitCode())
93 assert.Contains(t, res.Stderr.String(), "invalid CID")
94 })
95
96 t.Run("PeerID as input", func(t *testing.T) {
97 res := node.RunIPFS("cid", "inspect", "12D3KooWD3eckifWpRn9wQpMG9R9hX3sD158z7EqHWmweQAJU5SA")
98 assert.Equal(t, 1, res.ExitCode())
99 stderr := res.Stderr.String()
100 assert.Contains(t, stderr, "PeerID")
101 assert.Contains(t, stderr, "inspect its CID representation instead")
102 // suggested CID should use base36 (k prefix)
103 assert.Contains(t, stderr, "\n k")
104 })
105
106 t.Run("libp2p-key CID uses base36", func(t *testing.T) {
107 // Construct a libp2p-key CIDv1 from a known PeerID
108 pid, err := peer.Decode("12D3KooWD3eckifWpRn9wQpMG9R9hX3sD158z7EqHWmweQAJU5SA")
109 require.NoError(t, err)
110 pidCid := peer.ToCid(pid)
111 cidStr := pidCid.String()
112
113 res := node.RunIPFS("cid", "inspect", cidStr)
114 assert.Equal(t, 0, res.ExitCode())
115 out := res.Stdout.String()
116 assert.Contains(t, out, "Multicodec: libp2p-key (0x72)")
117 // CIDv1 should use base36 (k prefix)
118 assert.Contains(t, out, "CIDv1: k")
119 })
120
121 t.Run("identity multihash CID", func(t *testing.T) {
122 // raw codec + identity multihash: digest is the raw content ("test" = 74657374)
123 res := node.RunIPFS("cid", "inspect", "bafkqabdumvzxi")
124 assert.Equal(t, 0, res.ExitCode())
125 out := res.Stdout.String()
126 assert.Contains(t, out, "CID: bafkqabdumvzxi")
127 assert.Contains(t, out, "Multicodec: raw (0x55)")
128 assert.Contains(t, out, "Multihash: identity (0x0)")
129 assert.Contains(t, out, " Length: 4 bytes")
130 assert.Contains(t, out, " Digest: 74657374")
131 })
132
133 t.Run("unknown codec", func(t *testing.T) {
134 // Construct a CID with unknown codec 0x9999
135 mh, err := mhash.Sum([]byte("test"), mhash.SHA2_256, -1)
136 require.NoError(t, err)
137 unknownCID := cid.NewCidV1(0x9999, mh)
138 cidStr := unknownCID.String()
139
140 res := node.RunIPFS("cid", "inspect", cidStr)
141 assert.Equal(t, 0, res.ExitCode())
142 out := res.Stdout.String()
143 assert.Contains(t, out, "Multicodec: unknown (0x9999)")
144 assert.Contains(t, out, "not possible, requires dag-pb (0x70), got unknown (0x9999)")
145 })
146
147 t.Run("JSON output", func(t *testing.T) {
148 res := node.RunIPFS("cid", "inspect", "--enc=json", "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
149 assert.Equal(t, 0, res.ExitCode())
150
151 var result map[string]any
152 err := json.Unmarshal(res.Stdout.Bytes(), &result)
153 require.NoError(t, err)
154
155 // multibase.prefix should be a string, not a number
156 mb := result["multibase"].(map[string]any)
157 assert.IsType(t, "", mb["prefix"])
158 assert.Equal(t, "b", mb["prefix"])
159
160 // multihash.length should be a number (bytes)
161 mh := result["multihash"].(map[string]any)
162 assert.Equal(t, float64(32), mh["length"])
163
164 // cidV0 should be a clean CID string, no explanatory text
165 cidV0 := result["cidV0"].(string)
166 assert.True(t, strings.HasPrefix(cidV0, "Qm"), "cidV0 should be a valid CIDv0")
167
168 // cidV1 should be a clean CID string
169 cidV1 := result["cidV1"].(string)
170 assert.True(t, strings.HasPrefix(cidV1, "b"), "cidV1 should be base32 encoded")
171 })
172
173 t.Run("JSON output with empty CIDv0", func(t *testing.T) {
174 // raw codec can't be CIDv0
175 res := node.RunIPFS("cid", "inspect", "--enc=json", "bafkreigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
176 assert.Equal(t, 0, res.ExitCode())
177
178 var result map[string]any
179 err := json.Unmarshal(res.Stdout.Bytes(), &result)
180 require.NoError(t, err)
181
182 // cidV0 should not be present (omitempty)
183 _, hasCidV0 := result["cidV0"]
184 assert.False(t, hasCidV0, "cidV0 should be omitted when not possible")
185 })
186 }
187
188 // testCidBase32 tests 'ipfs cid base32' subcommand
189 // Includes regression tests for https://github.com/ipfs/kubo/issues/9007
190 func testCidBase32(t *testing.T) {
191 t.Parallel()
192 node := harness.NewT(t).NewNode()
193
194 t.Run("converts valid CIDs to base32", func(t *testing.T) {
195 t.Run("CIDv0 to base32", func(t *testing.T) {
196 res := node.RunIPFS("cid", "base32", "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
197 assert.Equal(t, 0, res.ExitCode())
198 assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa\n", res.Stdout.String())
199 })
200
201 t.Run("CIDv1 base58 to base32", func(t *testing.T) {
202 res := node.RunIPFS("cid", "base32", "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j")
203 assert.Equal(t, 0, res.ExitCode())
204 assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa\n", res.Stdout.String())
205 })
206
207 t.Run("already base32 CID remains unchanged", func(t *testing.T) {
208 res := node.RunIPFS("cid", "base32", "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
209 assert.Equal(t, 0, res.ExitCode())
210 assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa\n", res.Stdout.String())
211 })
212
213 t.Run("multiple valid CIDs", func(t *testing.T) {
214 res := node.RunIPFS("cid", "base32",
215 "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo",
216 "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
217 assert.Equal(t, 0, res.ExitCode())
218 assert.Empty(t, res.Stderr.String())
219 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
220 assert.Equal(t, 2, len(lines))
221 assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa", lines[0])
222 assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa", lines[1])
223 })
224 })
225
226 t.Run("error handling", func(t *testing.T) {
227 // Regression tests for https://github.com/ipfs/kubo/issues/9007
228 t.Run("returns error code 1 for single invalid CID", func(t *testing.T) {
229 res := node.RunIPFS("cid", "base32", "invalid-cid")
230 assert.Equal(t, 1, res.ExitCode())
231 assert.Contains(t, res.Stderr.String(), "invalid-cid: invalid cid")
232 assert.Contains(t, res.Stderr.String(), "Error: errors while displaying some entries")
233 })
234
235 t.Run("returns error code 1 for mixed valid and invalid CIDs", func(t *testing.T) {
236 res := node.RunIPFS("cid", "base32", "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo", "invalid-cid")
237 assert.Equal(t, 1, res.ExitCode())
238 // Valid CID should be converted and printed to stdout
239 assert.Contains(t, res.Stdout.String(), "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
240 // Invalid CID error should be printed to stderr
241 assert.Contains(t, res.Stderr.String(), "invalid-cid: invalid cid")
242 assert.Contains(t, res.Stderr.String(), "Error: errors while displaying some entries")
243 })
244
245 t.Run("returns error code 1 for stdin with invalid CIDs", func(t *testing.T) {
246 input := "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo\nbad-cid\nbafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa"
247 res := node.RunPipeToIPFS(strings.NewReader(input), "cid", "base32")
248 assert.Equal(t, 1, res.ExitCode())
249 // Valid CIDs should be converted
250 assert.Contains(t, res.Stdout.String(), "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
251 // Invalid CID error should be in stderr
252 assert.Contains(t, res.Stderr.String(), "bad-cid: invalid cid")
253 })
254 })
255 }
256
257 // testCidFormat tests 'ipfs cid format' subcommand
258 // Includes regression tests for https://github.com/ipfs/kubo/issues/9007
259 func testCidFormat(t *testing.T) {
260 t.Parallel()
261 node := harness.NewT(t).NewNode()
262
263 t.Run("formats CIDs with various options", func(t *testing.T) {
264 t.Run("default format preserves CID", func(t *testing.T) {
265 res := node.RunIPFS("cid", "format", "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
266 assert.Equal(t, 0, res.ExitCode())
267 assert.Equal(t, "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo\n", res.Stdout.String())
268 })
269
270 t.Run("convert to CIDv1 with base58btc", func(t *testing.T) {
271 res := node.RunIPFS("cid", "format", "-v", "1", "-b", "base58btc",
272 "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
273 assert.Equal(t, 0, res.ExitCode())
274 assert.Equal(t, "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j\n", res.Stdout.String())
275 })
276
277 t.Run("convert to CIDv0", func(t *testing.T) {
278 res := node.RunIPFS("cid", "format", "-v", "0",
279 "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
280 assert.Equal(t, 0, res.ExitCode())
281 assert.Equal(t, "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo\n", res.Stdout.String())
282 })
283
284 t.Run("change codec to raw", func(t *testing.T) {
285 res := node.RunIPFS("cid", "format", "--mc", "raw", "-b", "base32",
286 "bafybeievd6mwe6vcwnkwo3eizs3h7w3a34opszbyfxziqdxguhjw7imdve")
287 assert.Equal(t, 0, res.ExitCode())
288 assert.Equal(t, "bafkreievd6mwe6vcwnkwo3eizs3h7w3a34opszbyfxziqdxguhjw7imdve\n", res.Stdout.String())
289 })
290
291 t.Run("multiple valid CIDs with format options", func(t *testing.T) {
292 res := node.RunIPFS("cid", "format", "-v", "1", "-b", "base58btc",
293 "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo",
294 "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
295 assert.Equal(t, 0, res.ExitCode())
296 assert.Empty(t, res.Stderr.String())
297 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
298 assert.Equal(t, 2, len(lines))
299 assert.Equal(t, "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j", lines[0])
300 assert.Equal(t, "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j", lines[1])
301 })
302 })
303
304 t.Run("error handling", func(t *testing.T) {
305 // Regression tests for https://github.com/ipfs/kubo/issues/9007
306 t.Run("returns error code 1 for single invalid CID", func(t *testing.T) {
307 res := node.RunIPFS("cid", "format", "not-a-cid")
308 assert.Equal(t, 1, res.ExitCode())
309 assert.Contains(t, res.Stderr.String(), "not-a-cid: invalid cid")
310 assert.Contains(t, res.Stderr.String(), "Error: errors while displaying some entries")
311 })
312
313 t.Run("returns error code 1 for mixed valid and invalid CIDs", func(t *testing.T) {
314 res := node.RunIPFS("cid", "format", "not-a-cid", "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
315 assert.Equal(t, 1, res.ExitCode())
316 // Valid CID should be printed to stdout
317 assert.Contains(t, res.Stdout.String(), "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
318 // Invalid CID error should be printed to stderr
319 assert.Contains(t, res.Stderr.String(), "not-a-cid: invalid cid")
320 assert.Contains(t, res.Stderr.String(), "Error: errors while displaying some entries")
321 })
322
323 t.Run("returns error code 1 for stdin with invalid CIDs", func(t *testing.T) {
324 input := "invalid\nQmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo"
325 res := node.RunPipeToIPFS(strings.NewReader(input), "cid", "format", "-v", "1", "-b", "base58btc")
326 assert.Equal(t, 1, res.ExitCode())
327 // Valid CID should be converted
328 assert.Contains(t, res.Stdout.String(), "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j")
329 // Invalid CID error should be in stderr
330 assert.Contains(t, res.Stderr.String(), "invalid: invalid cid")
331 })
332 })
333 }
334
335 // testCidBases tests 'ipfs cid bases' subcommand
336 func testCidBases(t *testing.T) {
337 t.Parallel()
338 node := harness.NewT(t).NewNode()
339
340 t.Run("lists available bases", func(t *testing.T) {
341 // This is a regression test to ensure we don't accidentally add or remove support
342 // for multibase encodings. If a new base is intentionally added or removed,
343 // this test should be updated accordingly.
344 expectedBases := []string{
345 "identity",
346 "base2",
347 "base16",
348 "base16upper",
349 "base32",
350 "base32upper",
351 "base32pad",
352 "base32padupper",
353 "base32hex",
354 "base32hexupper",
355 "base32hexpad",
356 "base32hexpadupper",
357 "base36",
358 "base36upper",
359 "base58btc",
360 "base58flickr",
361 "base64",
362 "base64pad",
363 "base64url",
364 "base64urlpad",
365 "base256emoji",
366 }
367
368 res := node.RunIPFS("cid", "bases")
369 assert.Equal(t, 0, res.ExitCode())
370
371 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
372 assertExactSet(t, "bases", expectedBases, lines)
373 })
374
375 t.Run("with --prefix flag shows single letter prefixes", func(t *testing.T) {
376 // Regression test to catch any changes to the output format or supported bases
377 expectedLines := []string{
378 "identity",
379 "0 base2",
380 "b base32",
381 "B base32upper",
382 "c base32pad",
383 "C base32padupper",
384 "f base16",
385 "F base16upper",
386 "k base36",
387 "K base36upper",
388 "m base64",
389 "M base64pad",
390 "t base32hexpad",
391 "T base32hexpadupper",
392 "u base64url",
393 "U base64urlpad",
394 "v base32hex",
395 "V base32hexupper",
396 "z base58btc",
397 "Z base58flickr",
398 "🚀 base256emoji",
399 }
400
401 res := node.RunIPFS("cid", "bases", "--prefix")
402 assert.Equal(t, 0, res.ExitCode())
403
404 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
405 assertExactSet(t, "bases --prefix output", expectedLines, lines)
406 })
407
408 t.Run("with --numeric flag shows numeric codes", func(t *testing.T) {
409 // Regression test to catch any changes to the output format or supported bases
410 expectedLines := []string{
411 "0 identity",
412 "48 base2",
413 "98 base32",
414 "66 base32upper",
415 "99 base32pad",
416 "67 base32padupper",
417 "102 base16",
418 "70 base16upper",
419 "107 base36",
420 "75 base36upper",
421 "109 base64",
422 "77 base64pad",
423 "116 base32hexpad",
424 "84 base32hexpadupper",
425 "117 base64url",
426 "85 base64urlpad",
427 "118 base32hex",
428 "86 base32hexupper",
429 "122 base58btc",
430 "90 base58flickr",
431 "128640 base256emoji",
432 }
433
434 res := node.RunIPFS("cid", "bases", "--numeric")
435 assert.Equal(t, 0, res.ExitCode())
436
437 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
438 assertExactSet(t, "bases --numeric output", expectedLines, lines)
439 })
440
441 t.Run("with both --prefix and --numeric flags", func(t *testing.T) {
442 // Regression test to catch any changes to the output format or supported bases
443 expectedLines := []string{
444 "0 identity",
445 "0 48 base2",
446 "b 98 base32",
447 "B 66 base32upper",
448 "c 99 base32pad",
449 "C 67 base32padupper",
450 "f 102 base16",
451 "F 70 base16upper",
452 "k 107 base36",
453 "K 75 base36upper",
454 "m 109 base64",
455 "M 77 base64pad",
456 "t 116 base32hexpad",
457 "T 84 base32hexpadupper",
458 "u 117 base64url",
459 "U 85 base64urlpad",
460 "v 118 base32hex",
461 "V 86 base32hexupper",
462 "z 122 base58btc",
463 "Z 90 base58flickr",
464 "🚀 128640 base256emoji",
465 }
466
467 res := node.RunIPFS("cid", "bases", "--prefix", "--numeric")
468 assert.Equal(t, 0, res.ExitCode())
469
470 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
471 assertExactSet(t, "bases --prefix --numeric output", expectedLines, lines)
472 })
473 }
474
475 // testCidCodecs tests 'ipfs cid codecs' subcommand
476 func testCidCodecs(t *testing.T) {
477 t.Parallel()
478 node := harness.NewT(t).NewNode()
479
480 t.Run("lists available codecs", func(t *testing.T) {
481 // This is a regression test to ensure we don't accidentally add or remove
482 // IPLD codecs. If a codec is intentionally added or removed,
483 // this test should be updated accordingly.
484 expectedCodecs := []string{
485 "cbor",
486 "raw",
487 "dag-pb",
488 "dag-cbor",
489 "libp2p-key",
490 "git-raw",
491 "torrent-info",
492 "torrent-file",
493 "blake3-hashseq",
494 "leofcoin-block",
495 "leofcoin-tx",
496 "leofcoin-pr",
497 "dag-jose",
498 "dag-cose",
499 "eth-block",
500 "eth-block-list",
501 "eth-tx-trie",
502 "eth-tx",
503 "eth-tx-receipt-trie",
504 "eth-tx-receipt",
505 "eth-state-trie",
506 "eth-account-snapshot",
507 "eth-storage-trie",
508 "eth-receipt-log-trie",
509 "eth-receipt-log",
510 "bitcoin-block",
511 "bitcoin-tx",
512 "bitcoin-witness-commitment",
513 "zcash-block",
514 "zcash-tx",
515 "stellar-block",
516 "stellar-tx",
517 "decred-block",
518 "decred-tx",
519 "dash-block",
520 "dash-tx",
521 "swarm-manifest",
522 "swarm-feed",
523 "beeson",
524 "dag-json",
525 "swhid-1-snp",
526 "json",
527 "rdfc-1",
528 "json-jcs",
529 }
530
531 res := node.RunIPFS("cid", "codecs")
532 assert.Equal(t, 0, res.ExitCode())
533
534 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
535 assertExactSet(t, "codecs", expectedCodecs, lines)
536 })
537
538 t.Run("with --numeric flag shows codec numbers", func(t *testing.T) {
539 // This is a regression test to ensure we don't accidentally add or remove
540 // IPLD codecs. If a codec is intentionally added or removed,
541 // this test should be updated accordingly.
542 expectedLines := []string{
543 "81 cbor",
544 "85 raw",
545 "112 dag-pb",
546 "113 dag-cbor",
547 "114 libp2p-key",
548 "120 git-raw",
549 "123 torrent-info",
550 "124 torrent-file",
551 "128 blake3-hashseq",
552 "129 leofcoin-block",
553 "130 leofcoin-tx",
554 "131 leofcoin-pr",
555 "133 dag-jose",
556 "134 dag-cose",
557 "144 eth-block",
558 "145 eth-block-list",
559 "146 eth-tx-trie",
560 "147 eth-tx",
561 "148 eth-tx-receipt-trie",
562 "149 eth-tx-receipt",
563 "150 eth-state-trie",
564 "151 eth-account-snapshot",
565 "152 eth-storage-trie",
566 "153 eth-receipt-log-trie",
567 "154 eth-receipt-log",
568 "176 bitcoin-block",
569 "177 bitcoin-tx",
570 "178 bitcoin-witness-commitment",
571 "192 zcash-block",
572 "193 zcash-tx",
573 "208 stellar-block",
574 "209 stellar-tx",
575 "224 decred-block",
576 "225 decred-tx",
577 "240 dash-block",
578 "241 dash-tx",
579 "250 swarm-manifest",
580 "251 swarm-feed",
581 "252 beeson",
582 "297 dag-json",
583 "496 swhid-1-snp",
584 "512 json",
585 "46083 rdfc-1",
586 "46593 json-jcs",
587 }
588
589 res := node.RunIPFS("cid", "codecs", "--numeric")
590 assert.Equal(t, 0, res.ExitCode())
591
592 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
593 assertExactSet(t, "codecs --numeric output", expectedLines, lines)
594 })
595
596 t.Run("with --supported flag lists only supported codecs", func(t *testing.T) {
597 // This is a regression test to ensure we don't accidentally change the list
598 // of supported codecs. If a codec is intentionally added or removed from
599 // support, this test should be updated accordingly.
600 expectedSupportedCodecs := []string{
601 "cbor",
602 "dag-cbor",
603 "dag-jose",
604 "dag-json",
605 "dag-pb",
606 "git-raw",
607 "json",
608 "libp2p-key",
609 "raw",
610 }
611
612 res := node.RunIPFS("cid", "codecs", "--supported")
613 assert.Equal(t, 0, res.ExitCode())
614
615 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
616 assertExactSet(t, "supported codecs", expectedSupportedCodecs, lines)
617 })
618
619 t.Run("with both --supported and --numeric flags", func(t *testing.T) {
620 // Regression test to catch any changes to supported codecs or output format
621 expectedLines := []string{
622 "81 cbor",
623 "85 raw",
624 "112 dag-pb",
625 "113 dag-cbor",
626 "114 libp2p-key",
627 "120 git-raw",
628 "133 dag-jose",
629 "297 dag-json",
630 "512 json",
631 }
632
633 res := node.RunIPFS("cid", "codecs", "--supported", "--numeric")
634 assert.Equal(t, 0, res.ExitCode())
635
636 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
637 assertExactSet(t, "codecs --supported --numeric output", expectedLines, lines)
638 })
639 }
640
641 // testCidHashes tests 'ipfs cid hashes' subcommand
642 func testCidHashes(t *testing.T) {
643 t.Parallel()
644 node := harness.NewT(t).NewNode()
645
646 t.Run("lists available hashes", func(t *testing.T) {
647 // This is a regression test to ensure we don't accidentally add or remove
648 // support for hash functions. If a hash function is intentionally added
649 // or removed, this test should be updated accordingly.
650 expectedHashes := []string{
651 "identity",
652 "sha1",
653 "sha2-256",
654 "sha2-512",
655 "sha3-512",
656 "sha3-384",
657 "sha3-256",
658 "sha3-224",
659 "shake-256",
660 "keccak-224",
661 "keccak-256",
662 "keccak-384",
663 "keccak-512",
664 "blake3",
665 "dbl-sha2-256",
666 }
667
668 // Also expect all blake2b variants (160-512 in steps of 8)
669 for i := 160; i <= 512; i += 8 {
670 expectedHashes = append(expectedHashes, fmt.Sprintf("blake2b-%d", i))
671 }
672
673 // Also expect all blake2s variants (160-256 in steps of 8)
674 for i := 160; i <= 256; i += 8 {
675 expectedHashes = append(expectedHashes, fmt.Sprintf("blake2s-%d", i))
676 }
677
678 res := node.RunIPFS("cid", "hashes")
679 assert.Equal(t, 0, res.ExitCode())
680
681 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
682 assertExactSet(t, "hash functions", expectedHashes, lines)
683 })
684
685 t.Run("with --numeric flag shows hash function codes", func(t *testing.T) {
686 // This is a regression test to ensure we don't accidentally add or remove
687 // support for hash functions. If a hash function is intentionally added
688 // or removed, this test should be updated accordingly.
689 expectedLines := []string{
690 "0 identity",
691 "17 sha1",
692 "18 sha2-256",
693 "19 sha2-512",
694 "20 sha3-512",
695 "21 sha3-384",
696 "22 sha3-256",
697 "23 sha3-224",
698 "25 shake-256",
699 "26 keccak-224",
700 "27 keccak-256",
701 "28 keccak-384",
702 "29 keccak-512",
703 "30 blake3",
704 "86 dbl-sha2-256",
705 }
706
707 // Add all blake2b variants (160-512 in steps of 8)
708 for i := 160; i <= 512; i += 8 {
709 expectedLines = append(expectedLines, fmt.Sprintf("%d blake2b-%d", 45568+i/8, i))
710 }
711
712 // Add all blake2s variants (160-256 in steps of 8)
713 for i := 160; i <= 256; i += 8 {
714 expectedLines = append(expectedLines, fmt.Sprintf("%d blake2s-%d", 45632+i/8, i))
715 }
716
717 res := node.RunIPFS("cid", "hashes", "--numeric")
718 assert.Equal(t, 0, res.ExitCode())
719
720 lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
721 assertExactSet(t, "hashes --numeric output", expectedLines, lines)
722 })
723 }
724
725 // assertExactSet compares expected vs actual items and reports clear errors for any differences.
726 // This is used as a regression test to ensure we don't accidentally add or remove support.
727 // Both expected and actual strings are trimmed of whitespace before comparison for maintainability.
728 func assertExactSet(t *testing.T, itemType string, expected []string, actual []string) {
729 t.Helper()
730
731 // Normalize by trimming whitespace
732 normalizedExpected := make([]string, len(expected))
733 for i, item := range expected {
734 normalizedExpected[i] = strings.TrimSpace(item)
735 }
736
737 normalizedActual := make([]string, len(actual))
738 for i, item := range actual {
739 normalizedActual[i] = strings.TrimSpace(item)
740 }
741
742 expectedSet := make(map[string]bool)
743 for _, item := range normalizedExpected {
744 expectedSet[item] = true
745 }
746
747 actualSet := make(map[string]bool)
748 for _, item := range normalizedActual {
749 actualSet[item] = true
750 }
751
752 var missing []string
753 for _, item := range normalizedExpected {
754 if !actualSet[item] {
755 missing = append(missing, item)
756 }
757 }
758
759 var unexpected []string
760 for _, item := range normalizedActual {
761 if !expectedSet[item] {
762 unexpected = append(unexpected, item)
763 }
764 }
765
766 if len(missing) > 0 {
767 t.Errorf("Missing expected %s: %q", itemType, missing)
768 }
769 if len(unexpected) > 0 {
770 t.Errorf("Unexpected %s found: %q", itemType, unexpected)
771 }
772
773 assert.Equal(t, len(expected), len(actual),
774 "Expected %d %s but got %d", len(expected), itemType, len(actual))
775 }