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)
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) {