| 1 | package cli |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "regexp" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "github.com/blang/semver/v4" |
| 10 | "github.com/ipfs/kubo/test/cli/harness" |
| 11 | . "github.com/ipfs/kubo/test/cli/testutils" |
| 12 | "github.com/stretchr/testify/assert" |
| 13 | gomod "golang.org/x/mod/module" |
| 14 | ) |
| 15 | |
| 16 | var versionRegexp = regexp.MustCompile(`^ipfs version (.+)$`) |
| 17 | |
| 18 | func parseVersionOutput(s string) semver.Version { |
| 19 | versString := versionRegexp.FindStringSubmatch(s)[1] |
| 20 | v, err := semver.Parse(versString) |
| 21 | if err != nil { |
| 22 | panic(err) |
| 23 | } |
| 24 | return v |
| 25 | } |
| 26 | |
| 27 | func TestCurDirIsWritable(t *testing.T) { |
| 28 | t.Parallel() |
| 29 | h := harness.NewT(t) |
| 30 | h.WriteFile("test.txt", "It works!") |
| 31 | } |
| 32 | |
| 33 | func TestIPFSVersionCommandMatchesFlag(t *testing.T) { |
| 34 | t.Parallel() |
| 35 | node := harness.NewT(t).NewNode() |
| 36 | commandVersionStr := node.IPFS("version").Stdout.String() |
| 37 | commandVersionStr = strings.TrimSpace(commandVersionStr) |
| 38 | commandVersion := parseVersionOutput(commandVersionStr) |
| 39 | |
| 40 | flagVersionStr := node.IPFS("--version").Stdout.String() |
| 41 | flagVersionStr = strings.TrimSpace(flagVersionStr) |
| 42 | flagVersion := parseVersionOutput(flagVersionStr) |
| 43 | |
| 44 | assert.Equal(t, commandVersion, flagVersion) |
| 45 | } |
| 46 | |
| 47 | func TestIPFSVersionAll(t *testing.T) { |
| 48 | t.Parallel() |
| 49 | node := harness.NewT(t).NewNode() |
| 50 | res := node.IPFS("version", "--all").Stdout.String() |
| 51 | res = strings.TrimSpace(res) |
| 52 | assert.Contains(t, res, "Kubo version") |
| 53 | assert.Contains(t, res, "Repo version") |
| 54 | assert.Contains(t, res, "System version") |
| 55 | assert.Contains(t, res, "Golang version") |
| 56 | } |
| 57 | |
| 58 | func TestIPFSVersionDeps(t *testing.T) { |
| 59 | t.Parallel() |
| 60 | node := harness.NewT(t).NewNode() |
| 61 | res := node.IPFS("version", "deps").Stdout.String() |
| 62 | res = strings.TrimSpace(res) |
| 63 | lines := SplitLines(res) |
| 64 | |
| 65 | assert.True(t, strings.HasPrefix(lines[0], "github.com/ipfs/kubo@v")) |
| 66 | |
| 67 | for _, depLine := range lines[1:] { |
| 68 | split := strings.SplitSeq(depLine, " => ") |
| 69 | for moduleVersion := range split { |
| 70 | splitModVers := strings.Split(moduleVersion, "@") |
| 71 | modPath := splitModVers[0] |
| 72 | modVers := splitModVers[1] |
| 73 | // Skip local replace paths (starting with "./") |
| 74 | if strings.HasPrefix(modPath, "./") { |
| 75 | continue |
| 76 | } |
| 77 | assert.NoError(t, gomod.Check(modPath, modVers), "path: %s, version: %s", modPath, modVers) |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | func TestIPFSCommands(t *testing.T) { |
| 83 | t.Parallel() |
| 84 | node := harness.NewT(t).NewNode() |
| 85 | cmds := node.IPFSCommands() |
| 86 | assert.Contains(t, cmds, "ipfs add") |
| 87 | assert.Contains(t, cmds, "ipfs daemon") |
| 88 | assert.Contains(t, cmds, "ipfs update") |
| 89 | } |
| 90 | |
| 91 | func TestAllSubcommandsAcceptHelp(t *testing.T) { |
| 92 | t.Parallel() |
| 93 | node := harness.NewT(t).NewNode() |
| 94 | for _, cmd := range node.IPFSCommands() { |
| 95 | t.Run(fmt.Sprintf("command %q accepts help", cmd), func(t *testing.T) { |
| 96 | t.Parallel() |
| 97 | splitCmd := strings.Split(cmd, " ")[1:] |
| 98 | node.IPFS(StrCat("help", splitCmd)...) |
| 99 | node.IPFS(StrCat(splitCmd, "--help")...) |
| 100 | }) |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | func TestAllRootCommandsAreMentionedInHelpText(t *testing.T) { |
| 105 | t.Parallel() |
| 106 | node := harness.NewT(t).NewNode() |
| 107 | cmds := node.IPFSCommands() |
| 108 | var rootCmds []string |
| 109 | for _, cmd := range cmds { |
| 110 | splitCmd := strings.Split(cmd, " ") |
| 111 | if len(splitCmd) == 2 { |
| 112 | rootCmds = append(rootCmds, splitCmd[1]) |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | // a few base commands are not expected to be in the help message |
| 117 | // but we default to requiring them to be in the help message, so that we |
| 118 | // have to make a conscious decision to exclude them |
| 119 | notInHelp := map[string]bool{ |
| 120 | "object": true, |
| 121 | "shutdown": true, |
| 122 | } |
| 123 | |
| 124 | helpMsg := strings.TrimSpace(node.IPFS("--help").Stdout.String()) |
| 125 | for _, rootCmd := range rootCmds { |
| 126 | if _, ok := notInHelp[rootCmd]; ok { |
| 127 | continue |
| 128 | } |
| 129 | assert.Contains(t, helpMsg, fmt.Sprintf(" %s", rootCmd)) |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | func TestCommandDocsWidth(t *testing.T) { |
| 134 | t.Parallel() |
| 135 | node := harness.NewT(t).NewNode() |
| 136 | |
| 137 | // require new commands to explicitly opt in to longer lines |
| 138 | allowList := map[string]bool{ |
| 139 | "ipfs add": true, |
| 140 | "ipfs block put": true, |
| 141 | "ipfs daemon": true, |
| 142 | "ipfs config profile": true, |
| 143 | "ipfs pin remote service": true, |
| 144 | "ipfs name pubsub": true, |
| 145 | "ipfs object patch": true, |
| 146 | "ipfs swarm connect": true, |
| 147 | "ipfs p2p forward": true, |
| 148 | "ipfs p2p close": true, |
| 149 | "ipfs swarm disconnect": true, |
| 150 | "ipfs swarm addrs listen": true, |
| 151 | "ipfs dag resolve": true, |
| 152 | "ipfs dag get": true, |
| 153 | "ipfs pin remote add": true, |
| 154 | "ipfs config show": true, |
| 155 | "ipfs config edit": true, |
| 156 | "ipfs pin remote rm": true, |
| 157 | "ipfs pin remote ls": true, |
| 158 | "ipfs pin verify": true, |
| 159 | "ipfs pin remote service add": true, |
| 160 | "ipfs pin update": true, |
| 161 | "ipfs pin rm": true, |
| 162 | "ipfs p2p": true, |
| 163 | "ipfs resolve": true, |
| 164 | "ipfs dag stat": true, |
| 165 | "ipfs name publish": true, |
| 166 | "ipfs object diff": true, |
| 167 | "ipfs object patch add-link": true, |
| 168 | "ipfs name": true, |
| 169 | "ipfs diag profile": true, |
| 170 | "ipfs diag cmds": true, |
| 171 | "ipfs swarm addrs local": true, |
| 172 | "ipfs files ls": true, |
| 173 | "ipfs stats bw": true, |
| 174 | "ipfs swarm peers": true, |
| 175 | "ipfs pubsub sub": true, |
| 176 | "ipfs files write": true, |
| 177 | "ipfs swarm limit": true, |
| 178 | "ipfs commands completion fish": true, |
| 179 | "ipfs key export": true, |
| 180 | "ipfs routing get": true, |
| 181 | "ipfs refs": true, |
| 182 | "ipfs refs local": true, |
| 183 | "ipfs cid base32": true, |
| 184 | "ipfs pubsub pub": true, |
| 185 | "ipfs repo ls": true, |
| 186 | "ipfs routing put": true, |
| 187 | "ipfs key import": true, |
| 188 | "ipfs swarm peering add": true, |
| 189 | "ipfs swarm peering rm": true, |
| 190 | "ipfs swarm peering ls": true, |
| 191 | "ipfs update": true, |
| 192 | "ipfs swarm stats": true, |
| 193 | } |
| 194 | for _, cmd := range node.IPFSCommands() { |
| 195 | if _, ok := allowList[cmd]; ok { |
| 196 | continue |
| 197 | } |
| 198 | t.Run(fmt.Sprintf("command %q conforms to docs width limit", cmd), func(t *testing.T) { |
| 199 | splitCmd := strings.Split(cmd, " ") |
| 200 | resStr := node.IPFS(StrCat(splitCmd[1:], "--help")...) |
| 201 | res := strings.TrimSpace(resStr.Stdout.String()) |
| 202 | for _, line := range SplitLines(res) { |
| 203 | assert.LessOrEqualf(t, len(line), 80, "expected width %d < 80 for %q", len(line), cmd) |
| 204 | } |
| 205 | }) |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | func TestAllCommandsFailWhenPassedBadFlag(t *testing.T) { |
| 210 | t.Parallel() |
| 211 | node := harness.NewT(t).NewNode() |
| 212 | |
| 213 | for _, cmd := range node.IPFSCommands() { |
| 214 | t.Run(fmt.Sprintf("command %q fails when passed a bad flag", cmd), func(t *testing.T) { |
| 215 | splitCmd := strings.Split(cmd, " ") |
| 216 | res := node.RunIPFS(StrCat(splitCmd, "--badflag")...) |
| 217 | assert.Equal(t, 1, res.Cmd.ProcessState.ExitCode()) |
| 218 | }) |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | func TestCommandsFlags(t *testing.T) { |
| 223 | t.Parallel() |
| 224 | node := harness.NewT(t).NewNode() |
| 225 | resStr := node.IPFS("commands", "--flags").Stdout.String() |
| 226 | assert.Contains(t, resStr, "ipfs pin add --recursive / ipfs pin add -r") |
| 227 | assert.Contains(t, resStr, "ipfs id --format / ipfs id -f") |
| 228 | assert.Contains(t, resStr, "ipfs repo gc --quiet / ipfs repo gc -q") |
| 229 | } |